using dtPolyRef = System.UInt32;
using dtNodeIndex = System.UInt16;
using dtStatus = System.UInt32;
using System.Runtime.CompilerServices;
using System;

namespace Recast
{
	public unsafe partial class RecastGlobal
	{
		public const uint DT_SALT_BITS = 16;
		public const uint DT_TILE_BITS = 28;
		public const uint DT_POLY_BITS = 20;
		public const int DT_VERTS_PER_POLYGON = 6;
		public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V';
		public const int DT_NAVMESH_VERSION = 7;
		public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S';
		public const int DT_NAVMESH_STATE_VERSION = 1;
		public const ushort DT_EXT_LINK = 0x8000;
		public const uint DT_NULL_LINK = 0xffffffff;
		public const uint DT_OFFMESH_CON_BIDIR = 1;
		public const int DT_MAX_AREAS = 64;
		public const float DT_RAY_CAST_LIMIT_PROPORTIONS = 50.0f;
		public const dtNodeIndex DT_NULL_IDX = ~0 & ushort.MaxValue;
		public const int DT_NODE_PARENT_BITS = 24;
		public const int DT_NODE_STATE_BITS = 2;
		public const int DT_MAX_STATES_PER_NODE = 1 << DT_NODE_STATE_BITS;
		public const uint DT_FAILURE = 1u << 31;
		public const uint DT_SUCCESS = 1u << 30;
		public const uint DT_IN_PROGRESS = 1u << 29;
		public const uint DT_STATUS_DETAIL_MASK = 0x0ffffff;
		public const uint DT_WRONG_MAGIC = 1 << 0;
		public const uint DT_WRONG_VERSION = 1 << 1;
		public const uint DT_OUT_OF_MEMORY = 1 << 2;
		public const uint DT_INVALID_PARAM = 1 << 3;
		public const uint DT_BUFFER_TOO_SMALL = 1 << 4;
		public const uint DT_OUT_OF_NODES = 1 << 5;
		public const uint DT_PARTIAL_RESULT = 1 << 6;
		public const uint DT_ALREADY_OCCUPIED = 1 << 7;
		public static dtAllocFunc sAllocFunc = dtAllocDefault;
		public static dtFreeFunc sFreeFunc = dtFreeDefault;
		public static dtAssertFailFunc* sAssertFailFunc = null;
		public const ushort MESH_null_IDX = 0xffff;
		public const float H_SCALE = 0.999f;
		public const float RC_PI = 3.14159265f;
		public const int RC_SPAN_HEIGHT_BITS = 13;
		public const int RC_SPAN_MAX_HEIGHT = (1 << RC_SPAN_HEIGHT_BITS) - 1;
		public const int RC_SPANS_PER_POOL = 2048;
		public const ushort RC_BORDER_REG = 0x8000;
		public const ushort RC_MULTIPLE_REGS = 0;
		public const int RC_BORDER_VERTEX = 0x10000;
		public const int RC_AREA_BORDER = 0x20000;
		public const int RC_CONTOUR_REG_MASK = 0xffff;
		public const ushort RC_MESH_NULL_IDX = 0xffff;
		public const byte RC_null_AREA = 0;
		public const byte RC_WALKABLE_AREA = 63;
		public const int RC_NOT_CONNECTED = 0x3f;
		public static rcAllocFunc sRecastAllocFunc = rcAllocDefault;
		public static rcFreeFunc sRecastFreeFunc = rcFreeDefault;
		public const float EPSILON = 1e-6f;
		public static rcAssertFailFunc* sRecastAssertFailFunc;
		public const int RC_MAX_LAYERS = RC_MAX_LAYERS_DEF;
		public const int RC_MAX_NEIS = RC_MAX_NEIS_DEF;
		public const int VERTEX_BUCKET_COUNT = (1 << 12);
		public const uint RC_UNSET_HEIGHT = 0xffff;
		public const ushort RC_null_NEI = 0xffff;
		public delegate void* dtAllocFunc(int size, dtAllocHint hint);

		public delegate void dtFreeFunc(void* ptr);

		public static void dtAllocSetCustom(dtAllocFunc allocFunc, dtFreeFunc freeFunc)
		{
			sAllocFunc = allocFunc != null ? allocFunc : dtAllocDefault;
			sFreeFunc = freeFunc != null ? freeFunc : dtFreeDefault;
		}
		public static void* dtAlloc(int size, dtAllocHint hint)
		{
			return sAllocFunc(size, hint);
		}
		public static void dtFree(void* ptr)
		{
			if (ptr != null)
				sFreeFunc(ptr);
		}
		public delegate void dtAssertFailFunc(char* expression, char* file, int line);

		public static void dtAssertFailSetCustom(dtAssertFailFunc* assertFailFunc)
		{
			sAssertFailFunc = assertFailFunc;
		}
		public static dtAssertFailFunc* dtAssertFailGetCustom()
		{
			return sAssertFailFunc;
		}
		public static void dtIgnoreUnused<T>(T @t)
		{
		}
		public static void dtIgnoreUnused<T>(T* @t)
		{
		}
		public static void dtSwap<T>(ref T a, ref T b)
		{
			T t = a; a = b; b = t;
		}
		public static float dtMin(float a, float b)
		{
			return a < b ? a : b;
		}
		public static int dtMin(int a, int b)
		{
			return a < b ? a : b;
		}
		public static uint dtMin(uint a, uint b)
		{
			return a < b ? a : b;
		}
		public static float dtMax(float a, float b)
		{
			return a > b ? a : b;
		}
		public static int dtMax(int a, int b)
		{
			return a > b ? a : b;
		}
		public static float dtAbs(float a)
		{
			return a < 0 ? -a : a;
		}
		public static float dtSqr(float a)
		{
			return a * a;
		}
		public static float dtClamp(float v, float mn, float mx)
		{
			return v < mn ? mn : (v > mx ? mx : v);
		}

		public static void dtVcross(float* dest, float* v1, float* v2)
		{
			dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
			dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
			dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
		}
		public static float dtVdot(float* v1, float* v2)
		{
			return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
		}
		public static void dtVmad(float* dest, float* v1, float* v2, float s)
		{
			dest[0] = v1[0] + v2[0] * s;
			dest[1] = v1[1] + v2[1] * s;
			dest[2] = v1[2] + v2[2] * s;
		}
		public static void dtVlerp(float* dest, float* v1, float* v2, float t)
		{
			dest[0] = v1[0] + (v2[0] - v1[0]) * t;
			dest[1] = v1[1] + (v2[1] - v1[1]) * t;
			dest[2] = v1[2] + (v2[2] - v1[2]) * t;
		}
		public static void dtVadd(float* dest, float* v1, float* v2)
		{
			dest[0] = v1[0] + v2[0];
			dest[1] = v1[1] + v2[1];
			dest[2] = v1[2] + v2[2];
		}
		public static void dtVsub(float* dest, float* v1, float* v2)
		{
			dest[0] = v1[0] - v2[0];
			dest[1] = v1[1] - v2[1];
			dest[2] = v1[2] - v2[2];
		}
		public static void dtVscale(float* dest, float* v, float t)
		{
			dest[0] = v[0] * t;
			dest[1] = v[1] * t;
			dest[2] = v[2] * t;
		}
		public static void dtVmin(float* mn, float* v)
		{
			mn[0] = dtMin(mn[0], v[0]);
			mn[1] = dtMin(mn[1], v[1]);
			mn[2] = dtMin(mn[2], v[2]);
		}
		public static void dtVmax(float* mx, float* v)
		{
			mx[0] = dtMax(mx[0], v[0]);
			mx[1] = dtMax(mx[1], v[1]);
			mx[2] = dtMax(mx[2], v[2]);
		}
		public static void dtVset(float* dest, float x, float y, float z)
		{
			dest[0] = x; dest[1] = y; dest[2] = z;
		}
		public static void dtVcopy(float* dest, float* a)
		{
			dest[0] = a[0];
			dest[1] = a[1];
			dest[2] = a[2];
		}
		public static float dtVlen(float* v)
		{
			return dtMathSqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
		}
		public static float dtVlenSqr(float* v)
		{
			return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
		}
		public static float dtVdist(float* v1, float* v2)
		{
			float dx = v2[0] - v1[0];
			float dy = v2[1] - v1[1];
			float dz = v2[2] - v1[2];
			return dtMathSqrtf(dx * dx + dy * dy + dz * dz);
		}
		public static float dtVdistSqr(float* v1, float* v2)
		{
			float dx = v2[0] - v1[0];
			float dy = v2[1] - v1[1];
			float dz = v2[2] - v1[2];
			return dx * dx + dy * dy + dz * dz;
		}
		public static float dtVdist2D(float* v1, float* v2)
		{
			float dx = v2[0] - v1[0];
			float dz = v2[2] - v1[2];
			return dtMathSqrtf(dx * dx + dz * dz);
		}
		public static float dtVdist2DSqr(float* v1, float* v2)
		{
			float dx = v2[0] - v1[0];
			float dz = v2[2] - v1[2];
			return dx * dx + dz * dz;
		}
		public static void dtVnormalize(float* v)
		{
			float d = 1.0f / dtMathSqrtf(dtSqr(v[0]) + dtSqr(v[1]) + dtSqr(v[2]));
			v[0] *= d;
			v[1] *= d;
			v[2] *= d;
		}
		public static bool dtVequal(float* p0, float* p1)
		{
			float thr = dtSqr(1.0f / 16384.0f);
			float d = dtVdistSqr(p0, p1);
			return d < thr;
		}
		public static bool dtVisfinite(float* v)
		{
			bool result =
			dtMathIsfinite(v[0]) &&
			dtMathIsfinite(v[1]) &&
			dtMathIsfinite(v[2]);
			return result;
		}
		public static bool dtVisfinite2D(float* v)
		{
			bool result = dtMathIsfinite(v[0]) && dtMathIsfinite(v[2]);
			return result;
		}
		public static float dtVdot2D(float* u, float* v)
		{
			return u[0] * v[0] + u[2] * v[2];
		}
		public static float dtVperp2D(float* u, float* v)
		{
			return u[2] * v[0] - u[0] * v[2];
		}
		public static float dtTriArea2D(float* a, float* b, float* c)
		{
			float abx = b[0] - a[0];
			float abz = b[2] - a[2];
			float acx = c[0] - a[0];
			float acz = c[2] - a[2];
			return acx * abz - abx * acz;
		}
		public static bool dtOverlapQuantBounds(ushort* amin/*[3]*/, ushort* amax/*[3]*/, ushort* bmin/*[3]*/, ushort* bmax/*[3]*/)
		{
			bool overlap = true;
			overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
			overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
			overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
			return overlap;
		}
		public static bool dtOverlapBounds(float* amin, float* amax, float* bmin, float* bmax)
		{
			bool overlap = true;
			overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
			overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
			overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
			return overlap;
		}
		public static void dtClosestPtPointTriangle(float* closest, float* p, float* a, float* b, float* c)
		{
			float* ab = stackalloc float[3], ac = stackalloc float[3], ap = stackalloc float[3];
			dtVsub(ab, b, a);
			dtVsub(ac, c, a);
			dtVsub(ap, p, a);
			float d1 = dtVdot(ab, ap);
			float d2 = dtVdot(ac, ap);
			if (d1 <= 0.0f && d2 <= 0.0f)
			{
				dtVcopy(closest, a);
				return;
			}
			float* bp = stackalloc float[3];
			dtVsub(bp, p, b);
			float d3 = dtVdot(ab, bp);
			float d4 = dtVdot(ac, bp);
			if (d3 >= 0.0f && d4 <= d3)
			{
				dtVcopy(closest, b);
				return;
			}
			float vc = d1 * d4 - d3 * d2;
			if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f)
			{
				float v1 = d1 / (d1 - d3);
				closest[0] = a[0] + v1 * ab[0];
				closest[1] = a[1] + v1 * ab[1];
				closest[2] = a[2] + v1 * ab[2];
				return;
			}
			float* cp = stackalloc float[3];
			dtVsub(cp, p, c);
			float d5 = dtVdot(ab, cp);
			float d6 = dtVdot(ac, cp);
			if (d6 >= 0.0f && d5 <= d6)
			{
				dtVcopy(closest, c);
				return;
			}
			float vb = d5 * d2 - d1 * d6;
			if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f)
			{
				float w1 = d2 / (d2 - d6);
				closest[0] = a[0] + w1 * ac[0];
				closest[1] = a[1] + w1 * ac[1];
				closest[2] = a[2] + w1 * ac[2];
				return;
			}
			float va = d3 * d6 - d5 * d4;
			if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f)
			{
				float w2 = (d4 - d3) / ((d4 - d3) + (d5 - d6));
				closest[0] = b[0] + w2 * (c[0] - b[0]);
				closest[1] = b[1] + w2 * (c[1] - b[1]);
				closest[2] = b[2] + w2 * (c[2] - b[2]);
				return;
			}
			float denom = 1.0f / (va + vb + vc);
			float v = vb * denom;
			float w = vc * denom;
			closest[0] = a[0] + ab[0] * v + ac[0] * w;
			closest[1] = a[1] + ab[1] * v + ac[1] * w;
			closest[2] = a[2] + ab[2] * v + ac[2] * w;
		}
		public static bool dtClosestHeightPointTriangle(float* p, float* a, float* b, float* c, ref float h)
		{
			float EPS = 1e-6f;
			float* v0 = stackalloc float[3], v1 = stackalloc float[3], v2 = stackalloc float[3];
			dtVsub(v0, c, a);
			dtVsub(v1, b, a);
			dtVsub(v2, p, a);
			float denom = v0[0] * v1[2] - v0[2] * v1[0];
			if (fabsf(denom) < EPS)
				return false;
			float u = v1[2] * v2[0] - v1[0] * v2[2];
			float v = v0[0] * v2[2] - v0[2] * v2[0];
			if (denom < 0)
			{
				denom = -denom;
				u = -u;
				v = -v;
			}
			if (u >= 0.0f && v >= 0.0f && (u + v) <= denom)
			{
				h = a[1] + (v0[1] * u + v1[1] * v) / denom;
				return true;
			}
			return false;
		}
		public static bool dtIntersectSegmentPoly2D(float* p0, float* p1, float* verts, int nverts, ref float tmin, ref float tmax, ref int segMin, ref int segMax)
		{
			float EPS = 0.000001f;
			tmin = 0;
			tmax = 1;
			segMin = -1;
			segMax = -1;
			float* dir = stackalloc float[3];
			dtVsub(dir, p1, p0);
			float* edge = stackalloc float[3], diff = stackalloc float[3];
			for (int i = 0, j = nverts - 1; i < nverts; j = i++)
			{
				//float* edge = stackalloc float[3], diff = stackalloc float[3];
				memset(edge, 0, sizeof(float) * 3);
				memset(diff, 0, sizeof(float) * 3);
				dtVsub(edge, &verts[i * 3], &verts[j * 3]);
				dtVsub(diff, p0, &verts[j * 3]);
				float n = dtVperp2D(edge, diff);
				float d = dtVperp2D(dir, edge);
				if (fabsf(d) < EPS)
				{
					if (n < 0)
						return false;
					else
						continue;
				}
				float t = n / d;
				if (d < 0)
				{
					if (t > tmin)
					{
						tmin = t;
						segMin = j;
						if (tmin > tmax)
							return false;
					}
				}
				else
				{
					if (t < tmax)
					{
						tmax = t;
						segMax = j;
						if (tmax < tmin)
							return false;
					}
				}
			}
			return true;
		}
		public static bool dtIntersectSegSeg2D(float* ap, float* aq, float* bp, float* bq, ref float s, ref float t)
		{
			float* u = stackalloc float[3], v = stackalloc float[3], w = stackalloc float[3];
			dtVsub(u, aq, ap);
			dtVsub(v, bq, bp);
			dtVsub(w, ap, bp);
			float d = vperpXZ(u, v);
			if (fabsf(d) < 1e-6f) return false;
			s = vperpXZ(v, w) / d;
			t = vperpXZ(u, w) / d;
			return true;
		}
		public static bool dtPointInPolygon(float* pt, float* verts, int nverts)
		{
			int i, j;
			bool c = false;
			for (i = 0, j = nverts - 1; i < nverts; j = i++)
			{
				float* vi = &verts[i * 3];
				float* vj = &verts[j * 3];
				if (((vi[2] > pt[2]) != (vj[2] > pt[2])) &&
				(pt[0] < (vj[0] - vi[0]) * (pt[2] - vi[2]) / (vj[2] - vi[2]) + vi[0]))
					c = !c;
			}
			return c;
		}
		public static bool dtDistancePtPolyEdgesSqr(float* pt, float* verts, int nverts, float* ed, float* et)
		{
			int i, j;
			bool c = false;
			for (i = 0, j = nverts - 1; i < nverts; j = i++)
			{
				float* vi = &verts[i * 3];
				float* vj = &verts[j * 3];
				if (((vi[2] > pt[2]) != (vj[2] > pt[2])) &&
				(pt[0] < (vj[0] - vi[0]) * (pt[2] - vi[2]) / (vj[2] - vi[2]) + vi[0]))
					c = !c;
				ed[j] = dtDistancePtSegSqr2D(pt, vj, vi, ref et[j]);
			}
			return c;
		}
		public static float dtDistancePtSegSqr2D(float* pt, float* p, float* q, ref float t)
		{
			float pqx = q[0] - p[0];
			float pqz = q[2] - p[2];
			float dx = pt[0] - p[0];
			float dz = pt[2] - p[2];
			float d = pqx * pqx + pqz * pqz;
			t = pqx * dx + pqz * dz;
			if (d > 0) t /= d;
			if (t < 0) t = 0;
			else if (t > 1) t = 1;
			dx = p[0] + t * pqx - pt[0];
			dz = p[2] + t * pqz - pt[2];
			return dx * dx + dz * dz;
		}
		public static void dtCalcPolyCenter(float* tc, ushort* idx, int nidx, float* verts)
		{
			tc[0] = 0.0f;
			tc[1] = 0.0f;
			tc[2] = 0.0f;
			for (int j = 0; j < nidx; ++j)
			{
				float* v = &verts[idx[j] * 3];
				tc[0] += v[0];
				tc[1] += v[1];
				tc[2] += v[2];
			}
			float s = 1.0f / nidx;
			tc[0] *= s;
			tc[1] *= s;
			tc[2] *= s;
		}
		public static bool dtOverlapPolyPoly2D(float* polya, int npolya, float* polyb, int npolyb)
		{
			float eps = 1e-4f;
			float* n = stackalloc float[3];
			for (int i = 0, j = npolya - 1; i < npolya; j = i++)
			{
				float* va = &polya[j * 3];
				float* vb = &polya[i * 3];
				//float* n = stackalloc float[3] { vb[2] - va[2], 0, -(vb[0] - va[0]) };
				memset(n, 0, sizeof(float) * 3);
				n[0] = vb[2] - va[2];
				n[1] = 0;
				n[2] = -(vb[0] - va[0]);
				float amin = 0, amax = 0, bmin = 0, bmax = 0;
				projectPoly(n, polya, npolya, ref amin, ref amax);
				projectPoly(n, polyb, npolyb, ref bmin, ref bmax);
				if (!overlapRange(amin, amax, bmin, bmax, eps))
				{
					return false;
				}
			}
			for (int i = 0, j = npolyb - 1; i < npolyb; j = i++)
			{
				float* va = &polyb[j * 3];
				float* vb = &polyb[i * 3];
				//float* n = stackalloc float[3] { vb[2] - va[2], 0, -(vb[0] - va[0]) };
				memset(n, 0, sizeof(float) * 3);
				n[0] = vb[2] - va[2];
				n[1] = 0;
				n[2] = -(vb[0] - va[0]);
				float amin = 0, amax = 0, bmin = 0, bmax = 0;
				projectPoly(n, polya, npolya, ref amin, ref amax);
				projectPoly(n, polyb, npolyb, ref bmin, ref bmax);
				if (!overlapRange(amin, amax, bmin, bmax, eps))
				{
					return false;
				}
			}
			return true;
		}
		public static uint dtNextPow2(uint v)
		{
			v--;
			v |= v >> 1;
			v |= v >> 2;
			v |= v >> 4;
			v |= v >> 8;
			v |= v >> 16;
			v++;
			return v;
		}
		public static uint dtIlog2(int v)
		{
			uint r;
			uint shift;
			r = (v > 0xffff) ? 16U : 0U;
			v >>= (int)r;
			shift = (v > 0xff) ? 8U : 0U;
			v >>= (int)shift;
			r |= shift;
			shift = (v > 0xf) ? 4U : 0U;
			v >>= (int)shift;
			r |= shift;
			shift = (v > 0x3) ? 2U : 0U;
			v >>= (int)shift;
			r |= shift;
			r |= (uint)(v >> 1);
			return r;
		}
		public static int dtAlign4(int x)
		{ return (x + 3) & ~3; }
		public static int dtOppositeTile(int side)
		{ return (side + 4) & 0x7; }
		public static void dtSwapByte(byte* a, byte* b)
		{
			byte tmp = *a;
			*a = *b;
			*b = tmp;
		}
		public static void dtSwapEndian(ushort* v)
		{
			byte* x = (byte*)v;
			dtSwapByte(x + 0, x + 1);
		}
		public static void dtSwapEndian(short* v)
		{
			byte* x = (byte*)v;
			dtSwapByte(x + 0, x + 1);
		}
		public static void dtSwapEndian(uint* v)
		{
			byte* x = (byte*)v;
			dtSwapByte(x + 0, x + 3); dtSwapByte(x + 1, x + 2);
		}
		public static void dtSwapEndian(int* v)
		{
			byte* x = (byte*)v;
			dtSwapByte(x + 0, x + 3); dtSwapByte(x + 1, x + 2);
		}
		public static void dtSwapEndian(float* v)
		{
			byte* x = (byte*)v;
			dtSwapByte(x + 0, x + 3); dtSwapByte(x + 1, x + 2);
		}
		public static TypeToRetrieveAs* dtGetThenAdvanceBufferPointer<TypeToRetrieveAs>(ref byte* buffer, int distanceToAdvance)
		{
			TypeToRetrieveAs* returnPointer = reinterpretCast<TypeToRetrieveAs>(buffer);
			buffer += distanceToAdvance;
			return returnPointer;
		}
		public static float dtMathFabsf(float x)
		{
			return fabsf(x);
		}
		public static float dtMathSqrtf(float x)
		{
			return sqrtf(x);
		}
		public static float dtMathFloorf(float x)
		{
			return floorf(x);
		}
		public static float dtMathCeilf(float x)
		{
			return ceilf(x);
		}
		public static float dtMathCosf(float x)
		{
			return cosf(x);
		}
		public static float dtMathSinf(float x)
		{
			return sinf(x);
		}
		public static float dtMathAtan2f(float y, float x)
		{
			return atan2f(y, x);
		}
		public static bool dtMathIsfinite(float x)
		{
			return isfinite(x);
		}
		public static int dtGetDetailTriEdgeFlags(byte triFlags, int edgeIndex)
		{
			return (triFlags >> (edgeIndex * 2)) & 0x3;
		}
		public static dtNavMesh* dtAllocNavMesh()
		{
			return rcNew<dtNavMesh>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void dtFreeNavMesh(dtNavMesh* navmesh)
		{
			if (null == navmesh)
				return;
			rcDelete(navmesh);
		}
		public static bool dtCreateNavMeshData(dtNavMeshCreateParams* Params, byte** OutData, int* OutDataSize)
		{
			if (Params->nvp > DT_VERTS_PER_POLYGON)
				return false;
			if (Params->vertCount >= 0xffff)
				return false;
			if (0 == Params->vertCount || null == Params->verts)
				return false;
			if (0 == Params->polyCount || null == Params->polys)
				return false;
			int nvp = Params->nvp;
			byte* offMeshConClass = (byte*)0;
			int storedOffMeshConCount = 0;
			int offMeshConLinkCount = 0;
			if (Params->offMeshConCount > 0)
			{
				offMeshConClass = (byte*)dtAlloc(sizeof(byte) * Params->offMeshConCount * 2, dtAllocHint.DT_ALLOC_TEMP);
				if (null == offMeshConClass)
					return false;
				float hmin = FLT_MAX;
				float hmax = -FLT_MAX;
				if (Params->detailVerts != null && Params->detailVertsCount != 0)
				{
					for (int i = 0; i < Params->detailVertsCount; ++i)
					{
						float h = Params->detailVerts[i * 3 + 1];
						hmin = dtMin(hmin, h);
						hmax = dtMax(hmax, h);
					}
				}
				else
				{
					for (int i = 0; i < Params->vertCount; ++i)
					{
						ushort* iv = &Params->verts[i * 3];
						float h = Params->bmin[1] + iv[1] * Params->ch;
						hmin = dtMin(hmin, h);
						hmax = dtMax(hmax, h);
					}
				}
				hmin -= Params->walkableClimb;
				hmax += Params->walkableClimb;
				float* bmin = stackalloc float[3], bmax = stackalloc float[3];
				dtVcopy(bmin, Params->bmin);
				dtVcopy(bmax, Params->bmax);
				bmin[1] = hmin;
				bmax[1] = hmax;
				for (int i = 0; i < Params->offMeshConCount; ++i)
				{
					float* p0 = &Params->offMeshConVerts[(i * 2 + 0) * 3];
					float* p1 = &Params->offMeshConVerts[(i * 2 + 1) * 3];
					offMeshConClass[i * 2 + 0] = classifyOffMeshPoint(p0, bmin, bmax);
					offMeshConClass[i * 2 + 1] = classifyOffMeshPoint(p1, bmin, bmax);
					if (offMeshConClass[i * 2 + 0] == 0xff)
					{
						if (p0[1] < bmin[1] || p0[1] > bmax[1])
							offMeshConClass[i * 2 + 0] = 0;
					}
					if (offMeshConClass[i * 2 + 0] == 0xff)
						offMeshConLinkCount++;
					if (offMeshConClass[i * 2 + 1] == 0xff)
						offMeshConLinkCount++;
					if (offMeshConClass[i * 2 + 0] == 0xff)
						storedOffMeshConCount++;
				}
			}
			int totPolyCount = Params->polyCount + storedOffMeshConCount;
			int totVertCount = Params->vertCount + storedOffMeshConCount * 2;
			int edgeCount = 0;
			int portalCount = 0;
			for (int i = 0; i < Params->polyCount; ++i)
			{
				ushort* p = &Params->polys[i * 2 * nvp];
				for (int j = 0; j < nvp; ++j)
				{
					if (p[j] == MESH_null_IDX) break;
					edgeCount++;
					if ((p[nvp + j] & 0x8000) != 0)
					{
						ushort dir = (ushort)(p[nvp + j] & 0xf);
						if (dir != 0xf)
							portalCount++;
					}
				}
			}
			int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2;
			int uniqueDetailVertCount = 0;
			int detailTriCount = 0;
			if (Params->detailMeshes != null)
			{
				detailTriCount = Params->detailTriCount;
				for (int i = 0; i < Params->polyCount; ++i)
				{
					ushort* p = &Params->polys[i * nvp * 2];
					int ndv = (int)Params->detailMeshes[i * 4 + 1];
					int nv = 0;
					for (int j = 0; j < nvp; ++j)
					{
						if (p[j] == MESH_null_IDX) break;
						nv++;
					}
					ndv -= nv;
					uniqueDetailVertCount += ndv;
				}
			}
			else
			{
				uniqueDetailVertCount = 0;
				detailTriCount = 0;
				for (int i = 0; i < Params->polyCount; ++i)
				{
					ushort* p = &Params->polys[i * nvp * 2];
					int nv = 0;
					for (int j = 0; j < nvp; ++j)
					{
						if (p[j] == MESH_null_IDX) break;
						nv++;
					}
					detailTriCount += nv - 2;
				}
			}
			int headerSize = dtAlign4(sizeof(dtMeshHeader));
			int vertsSize = dtAlign4(sizeof(float) * 3 * totVertCount);
			int polysSize = dtAlign4(sizeof(dtPoly) * totPolyCount);
			int linksSize = dtAlign4(sizeof(dtLink) * maxLinkCount);
			int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail) * Params->polyCount);
			int detailVertsSize = dtAlign4(sizeof(float) * 3 * uniqueDetailVertCount);
			int detailTrisSize = dtAlign4(sizeof(byte) * 4 * detailTriCount);
			int bvTreeSize = Params->buildBvTree ? dtAlign4(sizeof(dtBVNode) * Params->polyCount * 2) : 0;
			int offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection) * storedOffMeshConCount);
			int dataSize = headerSize + vertsSize + polysSize + linksSize +
			detailMeshesSize + detailVertsSize + detailTrisSize +
			bvTreeSize + offMeshConsSize;

			byte* data = (byte*)dtAlloc(sizeof(byte) * dataSize, dtAllocHint.DT_ALLOC_PERM);
			if (null == data)
			{
				dtFree(offMeshConClass);
				return false;
			}
			memset(data, 0, dataSize);
			byte* d = data;
			dtMeshHeader* header = dtGetThenAdvanceBufferPointer<dtMeshHeader>(ref d, headerSize);
			float* navVerts = dtGetThenAdvanceBufferPointer<float>(ref d, vertsSize);
			dtPoly* navPolys = dtGetThenAdvanceBufferPointer<dtPoly>(ref d, polysSize);
			d += linksSize;
			dtPolyDetail* navDMeshes = dtGetThenAdvanceBufferPointer<dtPolyDetail>(ref d, detailMeshesSize);
			float* navDVerts = dtGetThenAdvanceBufferPointer<float>(ref d, detailVertsSize);
			byte* navDTris = dtGetThenAdvanceBufferPointer<byte>(ref d, detailTrisSize);
			dtBVNode* navBvtree = dtGetThenAdvanceBufferPointer<dtBVNode>(ref d, bvTreeSize);
			dtOffMeshConnection* offMeshCons = dtGetThenAdvanceBufferPointer<dtOffMeshConnection>(ref d, offMeshConsSize);
			header->magic = DT_NAVMESH_MAGIC;
			header->version = DT_NAVMESH_VERSION;
			header->x = Params->tileX;
			header->y = Params->tileY;
			header->layer = Params->tileLayer;
			header->userId = Params->userId;
			header->polyCount = totPolyCount;
			header->vertCount = totVertCount;
			header->maxLinkCount = maxLinkCount;
			dtVcopy(header->bmin, Params->bmin);
			dtVcopy(header->bmax, Params->bmax);
			header->detailMeshCount = Params->polyCount;
			header->detailVertCount = uniqueDetailVertCount;
			header->detailTriCount = detailTriCount;
			header->bvQuantFactor = 1.0f / Params->cs;
			header->offMeshBase = Params->polyCount;
			header->walkableHeight = Params->walkableHeight;
			header->walkableRadius = Params->walkableRadius;
			header->walkableClimb = Params->walkableClimb;
			header->offMeshConCount = storedOffMeshConCount;
			header->bvNodeCount = Params->buildBvTree ? Params->polyCount * 2 : 0;
			int offMeshVertsBase = Params->vertCount;
			int offMeshPolyBase = Params->polyCount;
			for (int i = 0; i < Params->vertCount; ++i)
			{
				ushort* iv = &Params->verts[i * 3];
				float* v = &navVerts[i * 3];
				v[0] = Params->bmin[0] + iv[0] * Params->cs;
				v[1] = Params->bmin[1] + iv[1] * Params->ch;
				v[2] = Params->bmin[2] + iv[2] * Params->cs;
			}
			int n = 0;
			for (int i = 0; i < Params->offMeshConCount; ++i)
			{
				if (offMeshConClass[i * 2 + 0] == 0xff)
				{
					float* linkv = &Params->offMeshConVerts[i * 2 * 3];
					float* v = &navVerts[(offMeshVertsBase + n * 2) * 3];
					dtVcopy(&v[0], &linkv[0]);
					dtVcopy(&v[3], &linkv[3]);
					n++;
				}
			}
			ushort* src = Params->polys;
			for (int i = 0; i < Params->polyCount; ++i)
			{
				dtPoly* p = &navPolys[i];
				p->vertCount = 0;
				p->flags = Params->polyFlags[i];
				p->setArea(Params->polyAreas[i]);
				p->setType((int)dtPolyTypes.DT_POLYTYPE_GROUND);
				for (int j = 0; j < nvp; ++j)
				{
					if (src[j] == MESH_null_IDX) break;
					p->verts[j] = src[j];
					if ((src[nvp + j] & 0x8000) != 0)
					{
						ushort dir = (ushort)(src[nvp + j] & 0xf);
						if (dir == 0xf)
							p->neis[j] = 0;
						else if (dir == 0)
							p->neis[j] = DT_EXT_LINK | 4;
						else if (dir == 1)
							p->neis[j] = DT_EXT_LINK | 2;
						else if (dir == 2)
							p->neis[j] = DT_EXT_LINK | 0;
						else if (dir == 3)
							p->neis[j] = DT_EXT_LINK | 6;
					}
					else
					{
						p->neis[j] = (ushort)(src[nvp + j] + 1);
					}
					p->vertCount++;
				}
				src += nvp * 2;
			}
			n = 0;
			for (int i = 0; i < Params->offMeshConCount; ++i)
			{
				if (offMeshConClass[i * 2 + 0] == 0xff)
				{
					dtPoly* p = &navPolys[offMeshPolyBase + n];
					p->vertCount = 2;
					p->verts[0] = (ushort)(offMeshVertsBase + n * 2 + 0);
					p->verts[1] = (ushort)(offMeshVertsBase + n * 2 + 1);
					p->flags = Params->offMeshConFlags[i];
					p->setArea(Params->offMeshConAreas[i]);
					p->setType((int)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION);
					n++;
				}
			}
			if (Params->detailMeshes != null)
			{
				ushort @base = 0;
				for (int i = 0; i < Params->polyCount; ++i)
				{
					ref dtPolyDetail dtl = ref navDMeshes[i];
					int vb = (int)Params->detailMeshes[i * 4 + 0];
					int ndv = (int)Params->detailMeshes[i * 4 + 1];
					int nv = navPolys[i].vertCount;
					dtl.vertBase = @base;
					dtl.vertCount = (byte)(ndv - nv);
					dtl.triBase = Params->detailMeshes[i * 4 + 2];
					dtl.triCount = (byte)Params->detailMeshes[i * 4 + 3];
					if ((ndv - nv) != 0)
					{
						memcpy(&navDVerts[@base * 3], &Params->detailVerts[(vb + nv) * 3], sizeof(float) * 3 * (ndv - nv));
						@base += (ushort)(ndv - nv);
					}
				}
				memcpy(navDTris, Params->detailTris, sizeof(byte) * 4 * Params->detailTriCount);
			}
			else
			{
				int @base = 0;
				for (int i = 0; i < Params->polyCount; ++i)
				{
					ref dtPolyDetail dtl = ref navDMeshes[i];
					int nv = navPolys[i].vertCount;
					dtl.vertBase = 0;
					dtl.vertCount = 0;
					dtl.triBase = (uint)@base;
					dtl.triCount = (byte)(nv - 2);
					for (int j = 2; j < nv; ++j)
					{
						byte* t = &navDTris[@base * 4];
						t[0] = 0;
						t[1] = (byte)(j - 1);
						t[2] = (byte)j;
						t[3] = (1 << 2);
						if (j == 2) t[3] |= (1 << 0);
						if (j == nv - 1) t[3] |= (1 << 4);
						@base++;
					}
				}
			}
			if (Params->buildBvTree)
			{
				createBVTree(Params, navBvtree, 2 * Params->polyCount);
			}
			n = 0;
			for (int i = 0; i < Params->offMeshConCount; ++i)
			{
				if (offMeshConClass[i * 2 + 0] == 0xff)
				{
					dtOffMeshConnection* con = &offMeshCons[n];
					con->poly = (ushort)(offMeshPolyBase + n);
					float* endPts = &Params->offMeshConVerts[i * 2 * 3];
					dtVcopy(&con->pos[0], &endPts[0]);
					dtVcopy(&con->pos[3], &endPts[3]);
					con->rad = Params->offMeshConRad[i];
					con->flags = (byte)(Params->offMeshConDir[i] != 0 ? DT_OFFMESH_CON_BIDIR : 0);
					con->side = offMeshConClass[i * 2 + 1];
					if (Params->offMeshConUserID != null)
						con->userId = Params->offMeshConUserID[i];
					n++;
				}
			}
			dtFree(offMeshConClass);
			*OutData = data;
			*OutDataSize = dataSize;
			return true;
		}
		public static dtNavMeshQuery* dtAllocNavMeshQuery()
		{
			return rcNew<dtNavMeshQuery>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static bool dtStatusSucceed(dtStatus status)
		{
			return (status & DT_SUCCESS) != 0;
		}
		public static bool dtStatusFailed(dtStatus status)
		{
			return (status & DT_FAILURE) != 0;
		}
		public static bool dtStatusInProgress(dtStatus status)
		{
			return (status & DT_IN_PROGRESS) != 0;
		}
		public static bool dtStatusDetail(dtStatus status, uint detail)
		{
			return (status & detail) != 0;
		}
		public static void* dtAllocDefault(int size, dtAllocHint @dtAllocHint)
		{
			return malloc(size);
		}
		public static void dtFreeDefault(void* ptr)
		{
			free(ptr);
		}
		public static void projectPoly(float* axis, float* poly, int npoly, ref float rmin, ref float rmax)
		{
			rmin = rmax = dtVdot2D(axis, &poly[0]);
			for (int i = 1; i < npoly; ++i)
			{
				float d = dtVdot2D(axis, &poly[i * 3]);
				rmin = dtMin(rmin, d);
				rmax = dtMax(rmax, d);
			}
		}
		public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps)
		{
			return ((amin + eps) > bmax || (amax - eps) < bmin) ? false : true;
		}
		public static void dtRandomPointInConvexPoly(float* pts, int npts, float* areas, float s, float t, float* Out_)
		{
			float areasum = 0.0f;
			for (int i = 2; i < npts; i++)
			{
				areas[i] = dtTriArea2D(&pts[0], &pts[(i - 1) * 3], &pts[i * 3]);
				areasum += dtMax(0.001f, areas[i]);
			}
			float thr = s * areasum;
			float acc = 0.0f;
			float u = 1.0f;
			int tri = npts - 1;
			for (int i = 2; i < npts; i++)
			{
				float dacc = areas[i];
				if (thr >= acc && thr < (acc + dacc))
				{
					u = (thr - acc) / dacc;
					tri = i;
					break;
				}
				acc += dacc;
			}
			float v = dtMathSqrtf(t);
			float a = 1 - v;
			float b = (1 - u) * v;
			float c = u * v;
			float* pa = &pts[0];
			float* pb = &pts[(tri - 1) * 3];
			float* pc = &pts[tri * 3];
			Out_[0] = a * pa[0] + b * pb[0] + c * pc[0];
			Out_[1] = a * pa[1] + b * pb[1] + c * pc[1];
			Out_[2] = a * pa[2] + b * pb[2] + c * pc[2];
		}
		public static float vperpXZ(float* a, float* b)
		{
			return a[0] * b[2] - a[2] * b[0];
		}
		public static bool overlapSlabs(float* amin, float* amax, float* bmin, float* bmax, float px, float py)
		{
			float minx = dtMax(amin[0] + px, bmin[0] + px);
			float maxx = dtMin(amax[0] - px, bmax[0] - px);
			if (minx > maxx)
				return false;
			float ad = (amax[1] - amin[1]) / (amax[0] - amin[0]);
			float ak = amin[1] - ad * amin[0];
			float bd = (bmax[1] - bmin[1]) / (bmax[0] - bmin[0]);
			float bk = bmin[1] - bd * bmin[0];
			float aminy = ad * minx + ak;
			float amaxy = ad * maxx + ak;
			float bminy = bd * minx + bk;
			float bmaxy = bd * maxx + bk;
			float dmin = bminy - aminy;
			float dmax = bmaxy - amaxy;
			if (dmin * dmax < 0)
				return true;
			float thr = dtSqr(py * 2);
			if (dmin * dmin <= thr || dmax * dmax <= thr)
				return true;
			return false;
		}
		public static float getSlabCoord(float* va, int side)
		{
			if (side == 0 || side == 4)
				return va[0];
			else if (side == 2 || side == 6)
				return va[2];
			return 0;
		}
		public static void calcSlabEndPoints(float* va, float* vb, float* bmin, float* bmax, int side)
		{
			if (side == 0 || side == 4)
			{
				if (va[2] < vb[2])
				{
					bmin[0] = va[2];
					bmin[1] = va[1];
					bmax[0] = vb[2];
					bmax[1] = vb[1];
				}
				else
				{
					bmin[0] = vb[2];
					bmin[1] = vb[1];
					bmax[0] = va[2];
					bmax[1] = va[1];
				}
			}
			else if (side == 2 || side == 6)
			{
				if (va[0] < vb[0])
				{
					bmin[0] = va[0];
					bmin[1] = va[1];
					bmax[0] = vb[0];
					bmax[1] = vb[1];
				}
				else
				{
					bmin[0] = vb[0];
					bmin[1] = vb[1];
					bmax[0] = va[0];
					bmax[1] = va[1];
				}
			}
		}
		public static int computeTileHash(int x, int y, int mask)
		{
			uint h1 = 0x8da6b343;
			uint h2 = 0xd8163841;
			uint n = (uint)(h1 * x + h2 * y);
			return (int)(n & mask);
		}
		public static uint allocLink(dtMeshTile* tile)
		{
			if (tile->linksFreeList == DT_NULL_LINK)
				return DT_NULL_LINK;
			uint link = tile->linksFreeList;
			tile->linksFreeList = tile->links[link].next;
			return link;
		}
		public static void freeLink(dtMeshTile* tile, uint link)
		{
			tile->links[link].next = tile->linksFreeList;
			tile->linksFreeList = link;
		}
		//public static bool onlyBoundary>
		public static void closestPointOnDetailEdges(bool onlyBoundary, dtMeshTile* tile, dtPoly* poly, float* pos, float* closest)
		{
			uint ip = (uint)(poly - tile->polys);
			dtPolyDetail* pd = &tile->detailMeshes[ip];
			float dmin = FLT_MAX;
			float tmin = 0;
			float* pmin = (float*)0;
			float* pmax = (float*)0;
			float** v = stackalloc float*[3];
			for (int i = 0; i < pd->triCount; i++)
			{
				byte* tris = &tile->detailTris[(pd->triBase + i) * 4];
				int ANY_BOUNDARY_EDGE =
			   ((int)dtDetailTriEdgeFlags.DT_DETAIL_EDGE_BOUNDARY << 0) |
			   ((int)dtDetailTriEdgeFlags.DT_DETAIL_EDGE_BOUNDARY << 2) |
			   ((int)dtDetailTriEdgeFlags.DT_DETAIL_EDGE_BOUNDARY << 4);
				if (onlyBoundary && (tris[3] & ANY_BOUNDARY_EDGE) == 0)
					continue;
				//float** v = stackalloc float*[3];
				memset(v, 0, sizeof(float*) * 3);
				for (int j = 0; j < 3; ++j)
				{
					if (tris[j] < poly->vertCount)
						v[j] = &tile->verts[poly->verts[tris[j]] * 3];
					else
						v[j] = &tile->detailVerts[(pd->vertBase + (tris[j] - poly->vertCount)) * 3];
				}
				for (int k = 0, j = 2; k < 3; j = k++)
				{
					if ((dtGetDetailTriEdgeFlags(tris[3], j) & (int)dtDetailTriEdgeFlags.DT_DETAIL_EDGE_BOUNDARY) == 0 &&
					(onlyBoundary || tris[j] < tris[k]))
					{
						continue;
					}
					float t = 0;
					float d = dtDistancePtSegSqr2D(pos, v[j], v[k], ref t);
					if (d < dmin)
					{
						dmin = d;
						tmin = t;
						pmin = v[j];
						pmax = v[k];
					}
				}
			}
			dtVlerp(closest, pmin, pmax, tmin);
		}
		public static int compareItemX(void* va, void* vb)
		{
			BVItem* a = (BVItem*)va;
			BVItem* b = (BVItem*)vb;
			if (a->bmin[0] < b->bmin[0])
				return -1;
			if (a->bmin[0] > b->bmin[0])
				return 1;
			return 0;
		}
		public static int compareItemY(void* va, void* vb)
		{
			BVItem* a = (BVItem*)va;
			BVItem* b = (BVItem*)vb;
			if (a->bmin[1] < b->bmin[1])
				return -1;
			if (a->bmin[1] > b->bmin[1])
				return 1;
			return 0;
		}
		public static int compareItemZ(void* va, void* vb)
		{
			BVItem* a = (BVItem*)va;
			BVItem* b = (BVItem*)vb;
			if (a->bmin[2] < b->bmin[2])
				return -1;
			if (a->bmin[2] > b->bmin[2])
				return 1;
			return 0;
		}
		public static void calcExtends(BVItem* items, int @int, int imin, int imax, ushort* bmin, ushort* bmax)
		{
			bmin[0] = items[imin].bmin[0];
			bmin[1] = items[imin].bmin[1];
			bmin[2] = items[imin].bmin[2];
			bmax[0] = items[imin].bmax[0];
			bmax[1] = items[imin].bmax[1];
			bmax[2] = items[imin].bmax[2];
			for (int i = imin + 1; i < imax; ++i)
			{
				ref BVItem it = ref items[i];
				if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0];
				if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1];
				if (it.bmin[2] < bmin[2]) bmin[2] = it.bmin[2];
				if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0];
				if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1];
				if (it.bmax[2] > bmax[2]) bmax[2] = it.bmax[2];
			}
		}
		public static int longestAxis(ushort x, ushort y, ushort z)
		{
			int axis = 0;
			ushort maxVal = x;
			if (y > maxVal)
			{
				axis = 1;
				maxVal = y;
			}
			if (z > maxVal)
			{
				axis = 2;
			}
			return axis;
		}
		public static void subdivide(BVItem* items, int nitems, int imin, int imax, ref int curNode, dtBVNode* nodes)
		{
			int inum = imax - imin;
			int icur = curNode;
			dtBVNode* node = &nodes[curNode++];
			if (inum == 1)
			{
				node->bmin[0] = items[imin].bmin[0];
				node->bmin[1] = items[imin].bmin[1];
				node->bmin[2] = items[imin].bmin[2];
				node->bmax[0] = items[imin].bmax[0];
				node->bmax[1] = items[imin].bmax[1];
				node->bmax[2] = items[imin].bmax[2];
				node->i = items[imin].i;
			}
			else
			{
				calcExtends(items, nitems, imin, imax, node->bmin, node->bmax);
				int axis = longestAxis((ushort)(node->bmax[0] - node->bmin[0]),
				 (ushort)(node->bmax[1] - node->bmin[1]),
				 (ushort)(node->bmax[2] - node->bmin[2]));
				if (axis == 0)
				{
					qsort(items + imin, inum, sizeof(BVItem), compareItemX);
				}
				else if (axis == 1)
				{
					qsort(items + imin, inum, sizeof(BVItem), compareItemY);
				}
				else
				{
					qsort(items + imin, inum, sizeof(BVItem), compareItemZ);
				}
				int isplit = imin + inum / 2;
				subdivide(items, nitems, imin, isplit, ref curNode, nodes);
				subdivide(items, nitems, isplit, imax, ref curNode, nodes);
				int iescape = curNode - icur;
				node->i = -iescape;
			}
		}
		public static int createBVTree(dtNavMeshCreateParams* Params, dtBVNode* nodes, int @int)
		{
			float quantFactor = 1 / Params->cs;
			BVItem* items = (BVItem*)dtAlloc(sizeof(BVItem) * Params->polyCount, dtAllocHint.DT_ALLOC_TEMP);
			float* bmin = stackalloc float[3];
			float* bmax = stackalloc float[3];
			for (int i = 0; i < Params->polyCount; i++)
			{
				ref BVItem it = ref items[i];
				it.i = i;
				if (Params->detailMeshes != null)
				{
					int vb = (int)Params->detailMeshes[i * 4 + 0];
					int ndv = (int)Params->detailMeshes[i * 4 + 1];
					//float* bmin = stackalloc float[3];
					//float* bmax = stackalloc float[3];
					memset(bmin, 0, sizeof(float) * 3);
					memset(bmax, 0, sizeof(float) * 3);
					float* dv = &Params->detailVerts[vb * 3];
					dtVcopy(bmin, dv);
					dtVcopy(bmax, dv);
					for (int j = 1; j < ndv; j++)
					{
						dtVmin(bmin, &dv[j * 3]);
						dtVmax(bmax, &dv[j * 3]);
					}
					it.bmin[0] = (ushort)dtClamp((int)((bmin[0] - Params->bmin[0]) * quantFactor), 0, 0xffff);
					it.bmin[1] = (ushort)dtClamp((int)((bmin[1] - Params->bmin[1]) * quantFactor), 0, 0xffff);
					it.bmin[2] = (ushort)dtClamp((int)((bmin[2] - Params->bmin[2]) * quantFactor), 0, 0xffff);
					it.bmax[0] = (ushort)dtClamp((int)((bmax[0] - Params->bmin[0]) * quantFactor), 0, 0xffff);
					it.bmax[1] = (ushort)dtClamp((int)((bmax[1] - Params->bmin[1]) * quantFactor), 0, 0xffff);
					it.bmax[2] = (ushort)dtClamp((int)((bmax[2] - Params->bmin[2]) * quantFactor), 0, 0xffff);
				}
				else
				{
					ushort* p = &Params->polys[i * Params->nvp * 2];
					it.bmin[0] = it.bmax[0] = Params->verts[p[0] * 3 + 0];
					it.bmin[1] = it.bmax[1] = Params->verts[p[0] * 3 + 1];
					it.bmin[2] = it.bmax[2] = Params->verts[p[0] * 3 + 2];
					for (int j = 1; j < Params->nvp; ++j)
					{
						if (p[j] == MESH_null_IDX) break;
						ushort x = Params->verts[p[j] * 3 + 0];
						ushort y = Params->verts[p[j] * 3 + 1];
						ushort z = Params->verts[p[j] * 3 + 2];
						if (x < it.bmin[0]) it.bmin[0] = x;
						if (y < it.bmin[1]) it.bmin[1] = y;
						if (z < it.bmin[2]) it.bmin[2] = z;
						if (x > it.bmax[0]) it.bmax[0] = x;
						if (y > it.bmax[1]) it.bmax[1] = y;
						if (z > it.bmax[2]) it.bmax[2] = z;
					}
					it.bmin[1] = (ushort)dtMathFloorf((float)it.bmin[1] * Params->ch / Params->cs);
					it.bmax[1] = (ushort)dtMathCeilf((float)it.bmax[1] * Params->ch / Params->cs);
				}
			}
			int curNode = 0;
			subdivide(items, Params->polyCount, 0, Params->polyCount, ref curNode, nodes);
			dtFree(items);
			return curNode;
		}
		const byte XP = 1 << 0;
		const byte ZP = 1 << 1;
		const byte XM = 1 << 2;
		const byte ZM = 1 << 3;
		public static byte classifyOffMeshPoint(float* pt, float* bmin, float* bmax)
		{
			byte Outcode = 0;
			Outcode = (byte)(Outcode | ((pt[0] >= bmax[0]) ? XP : 0));
			Outcode = (byte)(Outcode | ((pt[2] >= bmax[2]) ? ZP : 0));
			Outcode = (byte)(Outcode | ((pt[0] < bmin[0]) ? XM : 0));
			Outcode = (byte)(Outcode | ((pt[2] < bmin[2]) ? ZM : 0));
			switch (Outcode)
			{
				case XP: return 0;
				case XP | ZP: return 1;
				case ZP: return 2;
				case XM | ZP: return 3;
				case XM: return 4;
				case XM | ZM: return 5;
				case ZM: return 6;
				case XP | ZM: return 7;
			};
			return 0xff;
		}
		public static bool dtNavMeshHeaderSwapEndian(byte* data, int @int)
		{
			dtMeshHeader* header = (dtMeshHeader*)data;
			int swappedMagic = DT_NAVMESH_MAGIC;
			int swappedVersion = DT_NAVMESH_VERSION;
			dtSwapEndian(&swappedMagic);
			dtSwapEndian(&swappedVersion);
			if ((header->magic != DT_NAVMESH_MAGIC || header->version != DT_NAVMESH_VERSION) &&
			(header->magic != swappedMagic || header->version != swappedVersion))
			{
				return false;
			}
			dtSwapEndian(&header->magic);
			dtSwapEndian(&header->version);
			dtSwapEndian(&header->x);
			dtSwapEndian(&header->y);
			dtSwapEndian(&header->layer);
			dtSwapEndian(&header->userId);
			dtSwapEndian(&header->polyCount);
			dtSwapEndian(&header->vertCount);
			dtSwapEndian(&header->maxLinkCount);
			dtSwapEndian(&header->detailMeshCount);
			dtSwapEndian(&header->detailVertCount);
			dtSwapEndian(&header->detailTriCount);
			dtSwapEndian(&header->bvNodeCount);
			dtSwapEndian(&header->offMeshConCount);
			dtSwapEndian(&header->offMeshBase);
			dtSwapEndian(&header->walkableHeight);
			dtSwapEndian(&header->walkableRadius);
			dtSwapEndian(&header->walkableClimb);
			dtSwapEndian(&header->bmin[0]);
			dtSwapEndian(&header->bmin[1]);
			dtSwapEndian(&header->bmin[2]);
			dtSwapEndian(&header->bmax[0]);
			dtSwapEndian(&header->bmax[1]);
			dtSwapEndian(&header->bmax[2]);
			dtSwapEndian(&header->bvQuantFactor);
			return true;
		}
		public static bool dtNavMeshDataSwapEndian(byte* data, int @int)
		{
			dtMeshHeader* header = (dtMeshHeader*)data;
			if (header->magic != DT_NAVMESH_MAGIC)
				return false;
			if (header->version != DT_NAVMESH_VERSION)
				return false;
			int headerSize = dtAlign4(sizeof(dtMeshHeader));
			int vertsSize = dtAlign4(sizeof(float) * 3 * header->vertCount);
			int polysSize = dtAlign4(sizeof(dtPoly) * header->polyCount);
			int linksSize = dtAlign4(sizeof(dtLink) * (header->maxLinkCount));
			int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail) * header->detailMeshCount);
			int detailVertsSize = dtAlign4(sizeof(float) * 3 * header->detailVertCount);
			int detailTrisSize = dtAlign4(sizeof(byte) * 4 * header->detailTriCount);
			int bvtreeSize = dtAlign4(sizeof(dtBVNode) * header->bvNodeCount);
			int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection) * header->offMeshConCount);
			byte* d = data + headerSize;
			float* verts = dtGetThenAdvanceBufferPointer<float>(ref d, vertsSize);
			dtPoly* polys = dtGetThenAdvanceBufferPointer<dtPoly>(ref d, polysSize);
			d += linksSize;
			dtPolyDetail* detailMeshes = dtGetThenAdvanceBufferPointer<dtPolyDetail>(ref d, detailMeshesSize);
			float* detailVerts = dtGetThenAdvanceBufferPointer<float>(ref d, detailVertsSize);
			d += detailTrisSize;
			dtBVNode* bvTree = dtGetThenAdvanceBufferPointer<dtBVNode>(ref d, bvtreeSize);
			dtOffMeshConnection* offMeshCons = dtGetThenAdvanceBufferPointer<dtOffMeshConnection>(ref d, offMeshLinksSize);
			for (int i = 0; i < header->vertCount * 3; ++i)
			{
				dtSwapEndian(&verts[i]);
			}
			for (int i = 0; i < header->polyCount; ++i)
			{
				dtPoly* p = &polys[i];
				for (int j = 0; j < DT_VERTS_PER_POLYGON; ++j)
				{
					dtSwapEndian(&p->verts[j]);
					dtSwapEndian(&p->neis[j]);
				}
				dtSwapEndian(&p->flags);
			}
			for (int i = 0; i < header->detailMeshCount; ++i)
			{
				dtPolyDetail* pd = &detailMeshes[i];
				dtSwapEndian(&pd->vertBase);
				dtSwapEndian(&pd->triBase);
			}
			for (int i = 0; i < header->detailVertCount * 3; ++i)
			{
				dtSwapEndian(&detailVerts[i]);
			}
			for (int i = 0; i < header->bvNodeCount; ++i)
			{
				dtBVNode* node = &bvTree[i];
				for (int j = 0; j < 3; ++j)
				{
					dtSwapEndian(&node->bmin[j]);
					dtSwapEndian(&node->bmax[j]);
				}
				dtSwapEndian(&node->i);
			}
			for (int i = 0; i < header->offMeshConCount; ++i)
			{
				dtOffMeshConnection* con = &offMeshCons[i];
				for (int j = 0; j < 6; ++j)
					dtSwapEndian(&con->pos[j]);
				dtSwapEndian(&con->rad);
				dtSwapEndian(&con->poly);
			}
			return true;
		}
		public static void dtFreeNavMeshQuery(dtNavMeshQuery* navmesh)
		{
			if (null == navmesh) return;
			//navmesh->~dtNavMeshQuery();
			dtFree(navmesh);
		}
		public static void insertInterval(dtSegInterval* ints, ref int nints, int maxInts, short tmin, short tmax, dtPolyRef Ref)
		{
			if (nints + 1 > maxInts) return;
			int idx = 0;
			while (idx < nints)
			{
				if (tmax <= ints[idx].tmin)
					break;
				idx++;
			}
			if ((nints - idx) != 0)
				memmove(ints + idx + 1, ints + idx, sizeof(dtSegInterval) * (nints - idx));
			ints[idx].Ref = Ref;
			ints[idx].tmin = tmin;
			ints[idx].tmax = tmax;
			nints++;
		}
		public static uint dtHashRef(dtPolyRef a)
		{
			a += ~(a << 15);
			a ^= (a >> 10);
			a += (a << 3);
			a ^= (a >> 6);
			a += ~(a << 11);
			a ^= (a >> 16);
			return (uint)a;
		}
		public static void rcIgnoreUnused<T>(T @t)
		{
		}
		public static void rcIgnoreUnused<T>(T* @t)
		{
		}
		public static rcHeightfield* rcAllocHeightfield()
		{
			return rcNew<rcHeightfield>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void rcFreeHeightField(rcHeightfield* heightfield)
		{
			rcDelete(heightfield);
		}
		public static rcCompactHeightfield* rcAllocCompactHeightfield()
		{
			return rcNew<rcCompactHeightfield>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield)
		{
			rcDelete(compactHeightfield);
		}
		public static rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet()
		{
			return rcNew<rcHeightfieldLayerSet>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet)
		{
			rcDelete(layerSet);
		}
		public static rcContourSet* rcAllocContourSet()
		{
			return rcNew<rcContourSet>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void rcFreeContourSet(rcContourSet* contourSet)
		{
			rcDelete(contourSet);
		}
		public static rcPolyMesh* rcAllocPolyMesh()
		{
			return rcNew<rcPolyMesh>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void rcFreePolyMesh(rcPolyMesh* polyMesh)
		{
			rcDelete(polyMesh);
		}
		public static rcPolyMeshDetail* rcAllocPolyMeshDetail()
		{
			return rcNew<rcPolyMeshDetail>(rcAllocHint.RC_ALLOC_PERM);
		}
		public static void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh)
		{
			if (detailMesh == null)
			{
				return;
			}
			rcFree(detailMesh->meshes);
			rcFree(detailMesh->verts);
			rcFree(detailMesh->tris);
			rcFree(detailMesh);
		}
		public static void rcSwap<T>(ref T a, ref T b)
		{
			T t = a; a = b; b = t;
		}
		public static void rcSwap<T>(ref T* a, ref T* b)
		{
			T* t = a; a = b; b = t;
		}
		//public static T rcMin<T>(T a, T b)
		//{
		//    return a < b ? a : b;
		//}
		//public static T rcMax<T>(T a, T b)
		//{
		//    return a > b ? a : b;
		//}
		//public static T rcAbs<T>(T a)
		//{
		//    return a < 0 ? -a : a;
		//}
		//public static T rcSqr<T>(T a)
		//{
		//    return a * a;
		//}
		//public static T rcClamp<T>(T value, T minInclusive, T maxInclusive)
		//{
		//    return value < minInclusive ? minInclusive : (value > maxInclusive ? maxInclusive : value);
		//}

		public static float rcMin(float a, float b)
		{
			return a < b ? a : b;
		}
		public static int rcMin(int a, int b)
		{
			return a < b ? a : b;
		}
		public static ushort rcMin(ushort a, ushort b)
		{
			return a < b ? a : b;
		}
		public static float rcMax(float a, float b)
		{
			return a > b ? a : b;
		}
		public static uint rcMax(uint a, uint b)
		{
			return a > b ? a : b;
		}
		public static ushort rcMax(ushort a, ushort b)
		{
			return a > b ? a : b;
		}
		public static byte rcMax(byte a, byte b)
		{
			return a > b ? a : b;
		}
		public static int rcMax(int a, int b)
		{
			return a > b ? a : b;
		}
		public static float rcAbs(float a)
		{
			return a < 0 ? -a : a;
		}
		public static int rcAbs(int a)
		{
			return a < 0 ? -a : a;
		}
		public static float rcSqr(float a)
		{
			return a * a;
		}
		public static float rcClamp(float value, float minInclusive, float maxInclusive)
		{
			return value < minInclusive ? minInclusive : (value > maxInclusive ? maxInclusive : value);
		}
		public static int rcClamp(int value, int minInclusive, int maxInclusive)
		{
			return value < minInclusive ? minInclusive : (value > maxInclusive ? maxInclusive : value);
		}

		public static float rcSqrt(float x)
		{
			return sqrtf(x);
		}
		public static void rcVcross(float* dest, float* v1, float* v2)
		{
			dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
			dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
			dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
		}
		public static float rcVdot(float* v1, float* v2)
		{
			return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
		}
		public static void rcVmad(float* dest, float* v1, float* v2, float s)
		{
			dest[0] = v1[0] + v2[0] * s;
			dest[1] = v1[1] + v2[1] * s;
			dest[2] = v1[2] + v2[2] * s;
		}
		public static void rcVadd(float* dest, float* v1, float* v2)
		{
			dest[0] = v1[0] + v2[0];
			dest[1] = v1[1] + v2[1];
			dest[2] = v1[2] + v2[2];
		}
		public static void rcVsub(float* dest, float* v1, float* v2)
		{
			dest[0] = v1[0] - v2[0];
			dest[1] = v1[1] - v2[1];
			dest[2] = v1[2] - v2[2];
		}
		public static void rcVmin(float* mn, float* v)
		{
			mn[0] = rcMin(mn[0], v[0]);
			mn[1] = rcMin(mn[1], v[1]);
			mn[2] = rcMin(mn[2], v[2]);
		}
		public static void rcVmax(float* mx, float* v)
		{
			mx[0] = rcMax(mx[0], v[0]);
			mx[1] = rcMax(mx[1], v[1]);
			mx[2] = rcMax(mx[2], v[2]);
		}
		public static void rcVcopy(float* dest, float* v)
		{
			dest[0] = v[0];
			dest[1] = v[1];
			dest[2] = v[2];
		}
		public static float rcVdist(float* v1, float* v2)
		{
			float dx = v2[0] - v1[0];
			float dy = v2[1] - v1[1];
			float dz = v2[2] - v1[2];
			return rcSqrt(dx * dx + dy * dy + dz * dz);
		}
		public static float rcVdistSqr(float* v1, float* v2)
		{
			float dx = v2[0] - v1[0];
			float dy = v2[1] - v1[1];
			float dz = v2[2] - v1[2];
			return dx * dx + dy * dy + dz * dz;
		}
		public static void rcVnormalize(float* v)
		{
			float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2]));
			v[0] *= d;
			v[1] *= d;
			v[2] *= d;
		}
		public static void rcCalcBounds(float* verts, int numVerts, float* minBounds, float* maxBounds)
		{
			rcVcopy(minBounds, verts);
			rcVcopy(maxBounds, verts);
			for (int i = 1; i < numVerts; ++i)
			{
				float* v = &verts[i * 3];
				rcVmin(minBounds, v);
				rcVmax(maxBounds, v);
			}
		}
		public static void rcCalcGridSize(float* minBounds, float* maxBounds, float cellSize, ref int sizeX, ref int sizeZ)
		{
			sizeX = (int)((maxBounds[0] - minBounds[0]) / cellSize + 0.5f);
			sizeZ = (int)((maxBounds[2] - minBounds[2]) / cellSize + 0.5f);
		}
		public static bool rcCreateHeightfield(rcContext* context, ref rcHeightfield heightfield, int sizeX, int sizeZ, float* minBounds, float* maxBounds, float cellSize, float cellHeight)
		{
			rcIgnoreUnused(context);
			heightfield.width = sizeX;
			heightfield.height = sizeZ;
			rcHeightfield* heightfield_ptr = (rcHeightfield*)Unsafe.AsPointer(ref heightfield);
			rcVcopy(heightfield_ptr->bmin, minBounds);
			rcVcopy(heightfield_ptr->bmax, maxBounds);
			heightfield.cs = cellSize;
			heightfield.ch = cellHeight;
			heightfield.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*) * heightfield.width * heightfield.height, rcAllocHint.RC_ALLOC_PERM);
			if (null == heightfield.spans)
			{
				return false;
			}
			memset(heightfield.spans, 0, sizeof(rcSpan*) * heightfield.width * heightfield.height);
			return true;
		}
		public static void rcMarkWalkableTriangles(rcContext* context, float walkableSlopeAngle, float* verts, int numVerts, int* tris, int numTris, byte* triAreaIDs)
		{
			rcIgnoreUnused(context);
			rcIgnoreUnused(numVerts);
			float walkableThr = cosf(walkableSlopeAngle / 180.0f * RC_PI);
			float* norm = stackalloc float[3];
			for (int i = 0; i < numTris; ++i)
			{
				int* tri = &tris[i * 3];
				calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], norm);
				if (norm[1] > walkableThr)
				{
					triAreaIDs[i] = RC_WALKABLE_AREA;
				}
			}
		}
		public static void rcClearUnwalkableTriangles(rcContext* context, float walkableSlopeAngle, float* verts, int numVerts, int* tris, int numTris, byte* triAreaIDs)
		{
			rcIgnoreUnused(context);
			rcIgnoreUnused(numVerts);
			float walkableLimitY = cosf(walkableSlopeAngle / 180.0f * RC_PI);
			float* faceNormal = stackalloc float[3];
			for (int i = 0; i < numTris; ++i)
			{
				int* tri = &tris[i * 3];
				calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], faceNormal);
				if (faceNormal[1] <= walkableLimitY)
				{
					triAreaIDs[i] = RC_null_AREA;
				}
			}
		}
		public static bool rcAddSpan(rcContext* context, ref rcHeightfield heightfield, int x, int z, ushort spanMin, ushort spanMax, byte areaID, int flagMergeThreshold)
		{
			rcAssert(context);
			if (!addSpan(ref heightfield, x, z, spanMin, spanMax, areaID, flagMergeThreshold))
			{
				return false;
			}
			return true;
		}
		public static void rcFilterLowHangingWalkableObstacles(rcContext* context, int walkableClimb, ref rcHeightfield heightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_FILTER_LOW_OBSTACLES);
			int xSize = heightfield.width;
			int zSize = heightfield.height;
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					rcSpan* previousSpan = null;
					bool previousWasWalkable = false;
					byte previousArea = RC_null_AREA;
					for (rcSpan* span = heightfield.spans[x + z * xSize]; span != null; previousSpan = span, span = span->next)
					{
						bool walkable = span->area != RC_null_AREA;
						if (!walkable && previousWasWalkable)
						{
							if (rcAbs((int)span->smax - (int)previousSpan->smax) <= walkableClimb)
							{
								span->area = previousArea;
							}
						}
						previousWasWalkable = walkable;
						previousArea = (byte)span->area;
					}
				}
			}
		}
		public static void rcFilterLedgeSpans(rcContext* context, int walkableHeight, int walkableClimb, ref rcHeightfield heightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_FILTER_BORDER);
			int xSize = heightfield.width;
			int zSize = heightfield.height;
			int MAX_HEIGHT = 0xffff;
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					for (rcSpan* span = heightfield.spans[x + z * xSize]; span != null; span = span->next)
					{
						if (span->area == RC_null_AREA)
						{
							continue;
						}
						int bot = (int)(span->smax);
						int top = span->next != null ? (int)(span->next->smin) : MAX_HEIGHT;
						int minNeighborHeight = MAX_HEIGHT;
						int accessibleNeighborMinHeight = (int)span->smax;
						int accessibleNeighborMaxHeight = (int)span->smax;
						for (int direction = 0; direction < 4; ++direction)
						{
							int dx = x + rcGetDirOffsetX(direction);
							int dy = z + rcGetDirOffsetY(direction);
							if (dx < 0 || dy < 0 || dx >= xSize || dy >= zSize)
							{
								minNeighborHeight = rcMin(minNeighborHeight, -walkableClimb - bot);
								continue;
							}
							rcSpan* neighborSpan = heightfield.spans[dx + dy * xSize];
							int neighborBot = -walkableClimb;
							int neighborTop = neighborSpan != null ? (int)neighborSpan->smin : MAX_HEIGHT;
							if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight)
							{
								minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot);
							}
							for (neighborSpan = heightfield.spans[dx + dy * xSize]; neighborSpan != null; neighborSpan = neighborSpan->next)
							{
								neighborBot = (int)neighborSpan->smax;
								neighborTop = neighborSpan->next != null ? (int)neighborSpan->next->smin : MAX_HEIGHT;
								if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight)
								{
									minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot);
									if (rcAbs(neighborBot - bot) <= walkableClimb)
									{
										if (neighborBot < accessibleNeighborMinHeight) accessibleNeighborMinHeight = neighborBot;
										if (neighborBot > accessibleNeighborMaxHeight) accessibleNeighborMaxHeight = neighborBot;
									}
								}
							}
						}
						if (minNeighborHeight < -walkableClimb)
						{
							span->area = RC_null_AREA;
						}
						else if ((accessibleNeighborMaxHeight - accessibleNeighborMinHeight) > walkableClimb)
						{
							span->area = RC_null_AREA;
						}
					}
				}
			}
		}
		public static void rcFilterWalkableLowHeightSpans(rcContext* context, int walkableHeight, ref rcHeightfield heightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_FILTER_WALKABLE);
			int xSize = heightfield.width;
			int zSize = heightfield.height;
			int MAX_HEIGHT = 0xffff;
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					for (rcSpan* span = heightfield.spans[x + z * xSize]; span != null; span = span->next)
					{
						int bot = (int)(span->smax);
						int top = span->next != null ? (int)(span->next->smin) : MAX_HEIGHT;
						if ((top - bot) < walkableHeight)
						{
							span->area = RC_null_AREA;
						}
					}
				}
			}
		}
		public static int rcGetHeightFieldSpanCount(rcContext* context, ref rcHeightfield heightfield)
		{
			rcIgnoreUnused(context);
			int numCols = heightfield.width * heightfield.height;
			int spanCount = 0;
			for (int columnIndex = 0; columnIndex < numCols; ++columnIndex)
			{
				for (rcSpan* span = heightfield.spans[columnIndex]; span != null; span = span->next)
				{
					if (span->area != RC_null_AREA)
					{
						spanCount++;
					}
				}
			}
			return spanCount;
		}
		public static bool rcBuildCompactHeightfield(rcContext* context, int walkableHeight, int walkableClimb, ref rcHeightfield heightfield, ref rcCompactHeightfield compactHeightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
			int xSize = heightfield.width;
			int zSize = heightfield.height;
			int spanCount = rcGetHeightFieldSpanCount(context, ref heightfield);
			compactHeightfield.width = xSize;
			compactHeightfield.height = zSize;
			compactHeightfield.spanCount = spanCount;
			compactHeightfield.walkableHeight = walkableHeight;
			compactHeightfield.walkableClimb = walkableClimb;
			compactHeightfield.maxRegions = 0;
			rcCompactHeightfield* compactHeightfield_ptr = (rcCompactHeightfield*)Unsafe.AsPointer(ref compactHeightfield);
			rcHeightfield* heightfield_ptr = (rcHeightfield*)Unsafe.AsPointer(ref heightfield);
			rcVcopy(compactHeightfield_ptr->bmin, heightfield_ptr->bmin);
			rcVcopy(compactHeightfield_ptr->bmax, heightfield_ptr->bmax);
			compactHeightfield.bmax[1] += walkableHeight * heightfield.ch;
			compactHeightfield.cs = heightfield.cs;
			compactHeightfield.ch = heightfield.ch;
			compactHeightfield.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell) * xSize * zSize, rcAllocHint.RC_ALLOC_PERM);
			if (null == compactHeightfield.cells)
			{
				return false;
			}
			memset(compactHeightfield.cells, 0, sizeof(rcCompactCell) * xSize * zSize);
			compactHeightfield.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan) * spanCount, rcAllocHint.RC_ALLOC_PERM);
			if (null == compactHeightfield.spans)
			{
				return false;
			}
			memset(compactHeightfield.spans, 0, sizeof(rcCompactSpan) * spanCount);
			compactHeightfield.areas = (byte*)rcAlloc(sizeof(byte) * spanCount, rcAllocHint.RC_ALLOC_PERM);
			if (null == compactHeightfield.areas)
			{
				return false;
			}
			memset(compactHeightfield.areas, RC_null_AREA, sizeof(byte) * spanCount);
			int MAX_HEIGHT = 0xffff;
			int currentCellIndex = 0;
			int numColumns = xSize * zSize;
			for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex)
			{
				rcSpan* span = heightfield.spans[columnIndex];
				if (span == null)
				{
					continue;
				}
				ref rcCompactCell cell = ref compactHeightfield.cells[columnIndex];
				cell.index = (uint)currentCellIndex;
				cell.count = 0;
				for (; span != null; span = span->next)
				{
					if (span->area != RC_null_AREA)
					{
						int bot = (int)span->smax;
						int top = span->next != null ? (int)span->next->smin : MAX_HEIGHT;
						compactHeightfield.spans[currentCellIndex].y = (ushort)rcClamp(bot, 0, 0xffff);
						compactHeightfield.spans[currentCellIndex].h = (byte)rcClamp(top - bot, 0, 0xff);
						compactHeightfield.areas[currentCellIndex] = (byte)span->area;
						currentCellIndex++;
						cell.count++;
					}
				}
			}
			int MAX_LAYERS = RC_NOT_CONNECTED - 1;
			int maxLayerIndex = 0;
			int zStride = xSize;
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					for (int i = (int)cell.index, ni = (int)(cell.index + cell.count); i < ni; ++i)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[i];
						for (int dir = 0; dir < 4; ++dir)
						{
							rcSetCon(ref span, dir, RC_NOT_CONNECTED);
							int neighborX = x + rcGetDirOffsetX(dir);
							int neighborZ = z + rcGetDirOffsetY(dir);
							if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize)
							{
								continue;
							}
							ref rcCompactCell neighborCell = ref compactHeightfield.cells[neighborX + neighborZ * zStride];
							for (int k = (int)neighborCell.index, nk = (int)(neighborCell.index + neighborCell.count); k < nk; ++k)
							{
								ref rcCompactSpan neighborSpan = ref compactHeightfield.spans[k];
								int bot = rcMax(span.y, neighborSpan.y);
								int top = (int)rcMin(span.y + span.h, neighborSpan.y + neighborSpan.h);
								if ((top - bot) >= walkableHeight && rcAbs(neighborSpan.y - span.y) <= walkableClimb)
								{
									int layerIndex = k - (int)neighborCell.index;
									if (layerIndex < 0 || layerIndex > MAX_LAYERS)
									{
										maxLayerIndex = rcMax(maxLayerIndex, layerIndex);
										continue;
									}
									rcSetCon(ref span, dir, layerIndex);
									break;
								}
							}
						}
					}
				}
			}
			if (maxLayerIndex > MAX_LAYERS)
			{
				//maxLayerIndex, MAX_LAYERS);
			}
			return true;
		}
		public static bool rcErodeWalkableArea(rcContext* context, int erosionRadius, ref rcCompactHeightfield compactHeightfield)
		{
			rcAssert(context != null);
			int xSize = compactHeightfield.width;
			int zSize = compactHeightfield.height;
			ref int zStride = ref xSize;
			//rcScopedTimer timer(context, RC_TIMER_ERODE_AREA);
			byte* distanceToBoundary = (byte*)rcAlloc(sizeof(byte) * compactHeightfield.spanCount, rcAllocHint.RC_ALLOC_TEMP);
			if (null == distanceToBoundary)
			{
				return false;
			}
			memset(distanceToBoundary, 0xff, sizeof(byte) * compactHeightfield.spanCount);
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					for (int spanIndex = (int)cell.index, maxSpanIndex = (int)(cell.index + cell.count); spanIndex < maxSpanIndex; ++spanIndex)
					{
						if (compactHeightfield.areas[spanIndex] == RC_null_AREA)
						{
							distanceToBoundary[spanIndex] = 0;
							continue;
						}
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						int neighborCount = 0;
						for (int direction = 0; direction < 4; ++direction)
						{
							int neighborConnection = rcGetCon(ref span, direction);
							if (neighborConnection == RC_NOT_CONNECTED)
							{
								break;
							}
							int neighborX = x + rcGetDirOffsetX(direction);
							int neighborZ = z + rcGetDirOffsetY(direction);
							int neighborSpanIndex = (int)compactHeightfield.cells[neighborX + neighborZ * zStride].index + neighborConnection;
							if (compactHeightfield.areas[neighborSpanIndex] == RC_null_AREA)
							{
								break;
							}
							neighborCount++;
						}
						if (neighborCount != 4)
						{
							distanceToBoundary[spanIndex] = 0;
						}
					}
				}
			}
			byte newDistance;
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					int maxSpanIndex = (int)(cell.index + cell.count);
					for (int spanIndex = (int)cell.index; spanIndex < maxSpanIndex; ++spanIndex)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						if (rcGetCon(ref span, 0) != RC_NOT_CONNECTED)
						{
							int aX = x + rcGetDirOffsetX(0);
							int aY = z + rcGetDirOffsetY(0);
							int aIndex = (int)compactHeightfield.cells[aX + aY * xSize].index + rcGetCon(ref span, 0);
							ref rcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
							newDistance = (byte)rcMin((int)distanceToBoundary[aIndex] + 2, 255);
							if (newDistance < distanceToBoundary[spanIndex])
							{
								distanceToBoundary[spanIndex] = newDistance;
							}
							if (rcGetCon(ref aSpan, 3) != RC_NOT_CONNECTED)
							{
								int bX = aX + rcGetDirOffsetX(3);
								int bY = aY + rcGetDirOffsetY(3);
								int bIndex = (int)compactHeightfield.cells[bX + bY * xSize].index + rcGetCon(ref aSpan, 3);
								newDistance = (byte)rcMin((int)distanceToBoundary[bIndex] + 3, 255);
								if (newDistance < distanceToBoundary[spanIndex])
								{
									distanceToBoundary[spanIndex] = newDistance;
								}
							}
						}
						if (rcGetCon(ref span, 3) != RC_NOT_CONNECTED)
						{
							int aX = x + rcGetDirOffsetX(3);
							int aY = z + rcGetDirOffsetY(3);
							int aIndex = (int)compactHeightfield.cells[aX + aY * xSize].index + rcGetCon(ref span, 3);
							ref rcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
							newDistance = (byte)rcMin((int)distanceToBoundary[aIndex] + 2, 255);
							if (newDistance < distanceToBoundary[spanIndex])
							{
								distanceToBoundary[spanIndex] = newDistance;
							}
							if (rcGetCon(ref aSpan, 2) != RC_NOT_CONNECTED)
							{
								int bX = aX + rcGetDirOffsetX(2);
								int bY = aY + rcGetDirOffsetY(2);
								int bIndex = (int)compactHeightfield.cells[bX + bY * xSize].index + rcGetCon(ref aSpan, 2);
								newDistance = (byte)rcMin((int)distanceToBoundary[bIndex] + 3, 255);
								if (newDistance < distanceToBoundary[spanIndex])
								{
									distanceToBoundary[spanIndex] = newDistance;
								}
							}
						}
					}
				}
			}
			for (int z = zSize - 1; z >= 0; --z)
			{
				for (int x = xSize - 1; x >= 0; --x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					int maxSpanIndex = (int)(cell.index + cell.count);
					for (int spanIndex = (int)cell.index; spanIndex < maxSpanIndex; ++spanIndex)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						if (rcGetCon(ref span, 2) != RC_NOT_CONNECTED)
						{
							int aX = x + rcGetDirOffsetX(2);
							int aY = z + rcGetDirOffsetY(2);
							int aIndex = (int)compactHeightfield.cells[aX + aY * xSize].index + rcGetCon(ref span, 2);
							ref rcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
							newDistance = (byte)rcMin((int)distanceToBoundary[aIndex] + 2, 255);
							if (newDistance < distanceToBoundary[spanIndex])
							{
								distanceToBoundary[spanIndex] = newDistance;
							}
							if (rcGetCon(ref aSpan, 1) != RC_NOT_CONNECTED)
							{
								int bX = aX + rcGetDirOffsetX(1);
								int bY = aY + rcGetDirOffsetY(1);
								int bIndex = (int)compactHeightfield.cells[bX + bY * xSize].index + rcGetCon(ref aSpan, 1);
								newDistance = (byte)rcMin((int)distanceToBoundary[bIndex] + 3, 255);
								if (newDistance < distanceToBoundary[spanIndex])
								{
									distanceToBoundary[spanIndex] = newDistance;
								}
							}
						}
						if (rcGetCon(ref span, 1) != RC_NOT_CONNECTED)
						{
							int aX = x + rcGetDirOffsetX(1);
							int aY = z + rcGetDirOffsetY(1);
							int aIndex = (int)compactHeightfield.cells[aX + aY * xSize].index + rcGetCon(ref span, 1);
							ref rcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
							newDistance = (byte)rcMin((int)distanceToBoundary[aIndex] + 2, 255);
							if (newDistance < distanceToBoundary[spanIndex])
							{
								distanceToBoundary[spanIndex] = newDistance;
							}
							if (rcGetCon(ref aSpan, 0) != RC_NOT_CONNECTED)
							{
								int bX = aX + rcGetDirOffsetX(0);
								int bY = aY + rcGetDirOffsetY(0);
								int bIndex = (int)compactHeightfield.cells[bX + bY * xSize].index + rcGetCon(ref aSpan, 0);
								newDistance = (byte)rcMin((int)distanceToBoundary[bIndex] + 3, 255);
								if (newDistance < distanceToBoundary[spanIndex])
								{
									distanceToBoundary[spanIndex] = newDistance;
								}
							}
						}
					}
				}
			}
			byte minBoundaryDistance = (byte)(erosionRadius * 2);
			for (int spanIndex = 0; spanIndex < compactHeightfield.spanCount; ++spanIndex)
			{
				if (distanceToBoundary[spanIndex] < minBoundaryDistance)
				{
					compactHeightfield.areas[spanIndex] = RC_null_AREA;
				}
			}
			rcFree(distanceToBoundary);
			return true;
		}
		public static bool rcMedianFilterWalkableArea(rcContext* context, ref rcCompactHeightfield compactHeightfield)
		{
			rcAssert(context);
			int xSize = compactHeightfield.width;
			int zSize = compactHeightfield.height;
			int zStride = xSize;
			//rcScopedTimer timer(context, RC_TIMER_MEDIAN_AREA);
			byte* areas = (byte*)rcAlloc(sizeof(byte) * compactHeightfield.spanCount, rcAllocHint.RC_ALLOC_TEMP);
			if (null == areas)
			{
				//compactHeightfield.spanCount);
				return false;
			}
			memset(areas, 0xff, sizeof(byte) * compactHeightfield.spanCount);
			byte* neighborAreas = stackalloc byte[9];
			for (int z = 0; z < zSize; ++z)
			{
				for (int x = 0; x < xSize; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					int maxSpanIndex = (int)(cell.index + cell.count);
					for (int spanIndex = (int)cell.index; spanIndex < maxSpanIndex; ++spanIndex)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						if (compactHeightfield.areas[spanIndex] == RC_null_AREA)
						{
							areas[spanIndex] = compactHeightfield.areas[spanIndex];
							continue;
						}
						//byte* neighborAreas = stackalloc byte[9];
						memset(neighborAreas, 0, sizeof(byte) * 9);
						for (int neighborIndex = 0; neighborIndex < 9; ++neighborIndex)
						{
							neighborAreas[neighborIndex] = compactHeightfield.areas[spanIndex];
						}
						for (int dir = 0; dir < 4; ++dir)
						{
							if (rcGetCon(ref span, dir) == RC_NOT_CONNECTED)
							{
								continue;
							}
							int aX = x + rcGetDirOffsetX(dir);
							int aZ = z + rcGetDirOffsetY(dir);
							int aIndex = (int)compactHeightfield.cells[aX + aZ * zStride].index + rcGetCon(ref span, dir);
							if (compactHeightfield.areas[aIndex] != RC_null_AREA)
							{
								neighborAreas[dir * 2 + 0] = compactHeightfield.areas[aIndex];
							}
							ref rcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
							int dir2 = (dir + 1) & 0x3;
							int neighborConnection2 = rcGetCon(ref aSpan, dir2);
							if (neighborConnection2 != RC_NOT_CONNECTED)
							{
								int bX = aX + rcGetDirOffsetX(dir2);
								int bZ = aZ + rcGetDirOffsetY(dir2);
								int bIndex = (int)compactHeightfield.cells[bX + bZ * zStride].index + neighborConnection2;
								if (compactHeightfield.areas[bIndex] != RC_null_AREA)
								{
									neighborAreas[dir * 2 + 1] = compactHeightfield.areas[bIndex];
								}
							}
						}
						insertSort(neighborAreas, 9);
						areas[spanIndex] = neighborAreas[4];
					}
				}
			}
			memcpy(compactHeightfield.areas, areas, sizeof(byte) * compactHeightfield.spanCount);
			rcFree(areas);
			return true;
		}
		public static void rcMarkBoxArea(rcContext* context, float* boxMinBounds, float* boxMaxBounds, byte areaId, ref rcCompactHeightfield compactHeightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_MARK_BOX_AREA);
			int xSize = compactHeightfield.width;
			int zSize = compactHeightfield.height;
			int zStride = xSize;
			int minX = (int)((boxMinBounds[0] - compactHeightfield.bmin[0]) / compactHeightfield.cs);
			int minY = (int)((boxMinBounds[1] - compactHeightfield.bmin[1]) / compactHeightfield.ch);
			int minZ = (int)((boxMinBounds[2] - compactHeightfield.bmin[2]) / compactHeightfield.cs);
			int maxX = (int)((boxMaxBounds[0] - compactHeightfield.bmin[0]) / compactHeightfield.cs);
			int maxY = (int)((boxMaxBounds[1] - compactHeightfield.bmin[1]) / compactHeightfield.ch);
			int maxZ = (int)((boxMaxBounds[2] - compactHeightfield.bmin[2]) / compactHeightfield.cs);
			if (maxX < 0) { return; }
			if (minX >= xSize) { return; }
			if (maxZ < 0) { return; }
			if (minZ >= zSize) { return; }
			if (minX < 0) { minX = 0; }
			if (maxX >= xSize) { maxX = xSize - 1; }
			if (minZ < 0) { minZ = 0; }
			if (maxZ >= zSize) { maxZ = zSize - 1; }
			for (int z = minZ; z <= maxZ; ++z)
			{
				for (int x = minX; x <= maxX; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					int maxSpanIndex = (int)(cell.index + cell.count);
					for (int spanIndex = (int)cell.index; spanIndex < maxSpanIndex; ++spanIndex)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						if (span.y < minY || span.y > maxY)
						{
							continue;
						}
						if (compactHeightfield.areas[spanIndex] == RC_null_AREA)
						{
							continue;
						}
						compactHeightfield.areas[spanIndex] = areaId;
					}
				}
			}
		}
		public static void rcMarkConvexPolyArea(rcContext* context, float* verts, int numVerts, float minY, float maxY, byte areaId, ref rcCompactHeightfield compactHeightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_MARK_CONVEXPOLY_AREA);
			int xSize = compactHeightfield.width;
			int zSize = compactHeightfield.height;
			int zStride = xSize;
			float* bmin = stackalloc float[3];
			float* bmax = stackalloc float[3];
			rcVcopy(bmin, verts);
			rcVcopy(bmax, verts);
			for (int i = 1; i < numVerts; ++i)
			{
				rcVmin(bmin, &verts[i * 3]);
				rcVmax(bmax, &verts[i * 3]);
			}
			bmin[1] = minY;
			bmax[1] = maxY;
			int minx = (int)((bmin[0] - compactHeightfield.bmin[0]) / compactHeightfield.cs);
			int miny = (int)((bmin[1] - compactHeightfield.bmin[1]) / compactHeightfield.ch);
			int minz = (int)((bmin[2] - compactHeightfield.bmin[2]) / compactHeightfield.cs);
			int maxx = (int)((bmax[0] - compactHeightfield.bmin[0]) / compactHeightfield.cs);
			int maxy = (int)((bmax[1] - compactHeightfield.bmin[1]) / compactHeightfield.ch);
			int maxz = (int)((bmax[2] - compactHeightfield.bmin[2]) / compactHeightfield.cs);
			if (maxx < 0) { return; }
			if (minx >= xSize) { return; }
			if (maxz < 0) { return; }
			if (minz >= zSize) { return; }
			if (minx < 0) { minx = 0; }
			if (maxx >= xSize) { maxx = xSize - 1; }
			if (minz < 0) { minz = 0; }
			if (maxz >= zSize) { maxz = zSize - 1; }
			float* point = stackalloc float[3];
			for (int z = minz; z <= maxz; ++z)
			{
				for (int x = minx; x <= maxx; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					int maxSpanIndex = (int)(cell.index + cell.count);
					for (int spanIndex = (int)cell.index; spanIndex < maxSpanIndex; ++spanIndex)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						if (compactHeightfield.areas[spanIndex] == RC_null_AREA)
						{
							continue;
						}
						if ((int)span.y < miny || (int)span.y > maxy)
						{
							continue;
						}
						//float* point = stackalloc float[3] {
						//	compactHeightfield.bmin[0] + ((float)x + 0.5f) * compactHeightfield.cs,
						//	0,
						//	compactHeightfield.bmin[2] + ((float)z + 0.5f) * compactHeightfield.cs
						//};
						memset(point, 0, sizeof(float) * 3);
						point[0] = compactHeightfield.bmin[0] + ((float)x + 0.5f) * compactHeightfield.cs;
						point[1] = 0;
						point[2] = compactHeightfield.bmin[2] + ((float)z + 0.5f) * compactHeightfield.cs;
						if (pointInPoly(numVerts, verts, point))
						{
							compactHeightfield.areas[spanIndex] = areaId;
						}
					}
				}
			}
		}
		public static int rcOffsetPoly(float* verts, int numVerts, float offset, float* OutVerts, int maxOutVerts)
		{
			float MITER_LIMIT = 1.20f;
			int numOutVerts = 0;
			float* prevSegmentDir = stackalloc float[3];
			float* currSegmentDir = stackalloc float[3];
			for (int vertIndex = 0; vertIndex < numVerts; vertIndex++)
			{

				int vertIndexA = (vertIndex + numVerts - 1) % numVerts;
				int vertIndexB = vertIndex;
				int vertIndexC = (vertIndex + 1) % numVerts;
				float* vertA = &verts[vertIndexA * 3];
				float* vertB = &verts[vertIndexB * 3];
				float* vertC = &verts[vertIndexC * 3];

				//float* prevSegmentDir = stackalloc float[3];
				memset(prevSegmentDir, 0, sizeof(float) * 3);
				rcVsub(prevSegmentDir, vertB, vertA);
				prevSegmentDir[1] = 0;
				rcVsafeNormalize(prevSegmentDir);

				//float* currSegmentDir = stackalloc float[3];
				memset(currSegmentDir, 0, sizeof(float) * 3);
				rcVsub(currSegmentDir, vertC, vertB);
				currSegmentDir[1] = 0;
				rcVsafeNormalize(currSegmentDir);



				float cross = currSegmentDir[0] * prevSegmentDir[2] - prevSegmentDir[0] * currSegmentDir[2];

				float prevSegmentNormX = -prevSegmentDir[2];
				float prevSegmentNormZ = prevSegmentDir[0];

				float currSegmentNormX = -currSegmentDir[2];
				float currSegmentNormZ = currSegmentDir[0];



				float cornerMiterX = (prevSegmentNormX + currSegmentNormX) * 0.5f;
				float cornerMiterZ = (prevSegmentNormZ + currSegmentNormZ) * 0.5f;
				float cornerMiterSqMag = rcSqr(cornerMiterX) + rcSqr(cornerMiterZ);


				bool bevel = cornerMiterSqMag * MITER_LIMIT * MITER_LIMIT < 1.0f;

				if (cornerMiterSqMag > EPSILON)
				{
					float scale = 1.0f / cornerMiterSqMag;
					cornerMiterX *= scale;
					cornerMiterZ *= scale;
				}
				if (bevel && cross < 0.0f)
				{
					if (numOutVerts + 2 > maxOutVerts)
					{
						return 0;
					}


					float d = (1.0f - (prevSegmentDir[0] * currSegmentDir[0] + prevSegmentDir[2] * currSegmentDir[2])) * 0.5f;
					OutVerts[numOutVerts * 3 + 0] = vertB[0] + (-prevSegmentNormX + prevSegmentDir[0] * d) * offset;
					OutVerts[numOutVerts * 3 + 1] = vertB[1];
					OutVerts[numOutVerts * 3 + 2] = vertB[2] + (-prevSegmentNormZ + prevSegmentDir[2] * d) * offset;
					numOutVerts++;
					OutVerts[numOutVerts * 3 + 0] = vertB[0] + (-currSegmentNormX - currSegmentDir[0] * d) * offset;
					OutVerts[numOutVerts * 3 + 1] = vertB[1];
					OutVerts[numOutVerts * 3 + 2] = vertB[2] + (-currSegmentNormZ - currSegmentDir[2] * d) * offset;
					numOutVerts++;
				}
				else
				{
					if (numOutVerts + 1 > maxOutVerts)
					{
						return 0;
					}

					OutVerts[numOutVerts * 3 + 0] = vertB[0] - cornerMiterX * offset;
					OutVerts[numOutVerts * 3 + 1] = vertB[1];
					OutVerts[numOutVerts * 3 + 2] = vertB[2] - cornerMiterZ * offset;
					numOutVerts++;
				}
			}
			return numOutVerts;
		}
		public static void rcMarkCylinderArea(rcContext* context, float* position, float radius, float height, byte areaId, ref rcCompactHeightfield compactHeightfield)
		{
			rcAssert(context);
			//rcScopedTimer timer(context, RC_TIMER_MARK_CYLINDER_AREA);
			int xSize = compactHeightfield.width;
			int zSize = compactHeightfield.height;
			int zStride = xSize;
			float* cylinderBBMin = stackalloc float[]
		   {
				position[0] - radius,
				position[1],
				position[2] - radius
			};
			float* cylinderBBMax = stackalloc float[]
		   {
				position[0] + radius,
				position[1] + height,
				position[2] + radius
			};
			int minx = (int)((cylinderBBMin[0] - compactHeightfield.bmin[0]) / compactHeightfield.cs);
			int miny = (int)((cylinderBBMin[1] - compactHeightfield.bmin[1]) / compactHeightfield.ch);
			int minz = (int)((cylinderBBMin[2] - compactHeightfield.bmin[2]) / compactHeightfield.cs);
			int maxx = (int)((cylinderBBMax[0] - compactHeightfield.bmin[0]) / compactHeightfield.cs);
			int maxy = (int)((cylinderBBMax[1] - compactHeightfield.bmin[1]) / compactHeightfield.ch);
			int maxz = (int)((cylinderBBMax[2] - compactHeightfield.bmin[2]) / compactHeightfield.cs);
			if (maxx < 0) { return; }
			if (minx >= xSize) { return; }
			if (maxz < 0) { return; }
			if (minz >= zSize) { return; }
			if (minx < 0) { minx = 0; }
			if (maxx >= xSize) { maxx = xSize - 1; }
			if (minz < 0) { minz = 0; }
			if (maxz >= zSize) { maxz = zSize - 1; }
			float radiusSq = radius * radius;
			for (int z = minz; z <= maxz; ++z)
			{
				for (int x = minx; x <= maxx; ++x)
				{
					ref rcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
					int maxSpanIndex = (int)(cell.index + cell.count);
					float cellX = compactHeightfield.bmin[0] + ((float)x + 0.5f) * compactHeightfield.cs;
					float cellZ = compactHeightfield.bmin[2] + ((float)z + 0.5f) * compactHeightfield.cs;
					float deltaX = cellX - position[0];
					float deltaZ = cellZ - position[2];
					if (rcSqr(deltaX) + rcSqr(deltaZ) >= radiusSq)
					{
						continue;
					}
					for (int spanIndex = (int)cell.index; spanIndex < maxSpanIndex; ++spanIndex)
					{
						ref rcCompactSpan span = ref compactHeightfield.spans[spanIndex];
						if (compactHeightfield.areas[spanIndex] == RC_null_AREA)
						{
							continue;
						}
						if ((int)span.y >= miny && (int)span.y <= maxy)
						{
							compactHeightfield.areas[spanIndex] = areaId;
						}
					}
				}
			}
		}
		public static bool rcBuildDistanceField(rcContext* ctx, ref rcCompactHeightfield chf)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_DISTANCEFIELD);
			if (chf.dist != null)
			{
				rcFree(chf.dist);
				chf.dist = null;
			}
			ushort* src = (ushort*)rcAlloc(sizeof(ushort) * chf.spanCount, rcAllocHint.RC_ALLOC_TEMP);
			if (null == src)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' (%d).", chf.spanCount);
				return false;
			}
			ushort* dst = (ushort*)rcAlloc(sizeof(ushort) * chf.spanCount, rcAllocHint.RC_ALLOC_TEMP);
			if (null == dst)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' (%d).", chf.spanCount);
				rcFree(src);
				return false;
			}
			ushort maxDist = 0;
			{
				//rcScopedTimer timerDist(ctx, RC_TIMER_BUILD_DISTANCEFIELD_DIST);
				calculateDistanceField(ref chf, src, ref maxDist);
				chf.maxDistance = maxDist;
			}
			{
				//rcScopedTimer timerBlur(ctx, RC_TIMER_BUILD_DISTANCEFIELD_BLUR);
				if (boxBlur(ref chf, 1, src, dst) != src)
					rcSwap(ref src, ref dst);
				chf.dist = src;
			}
			rcFree(dst);
			return true;
		}
		public static bool rcBuildRegions(rcContext* ctx, ref rcCompactHeightfield chf, int borderSize, int minRegionArea, int mergeRegionArea)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);
			int w = chf.width;
			int h = chf.height;
			rcScopedDelete<ushort> buf = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * chf.spanCount * 2, rcAllocHint.RC_ALLOC_TEMP));
			if (null == buf)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount * 4);
				return false;
			}
			////ctx->startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_WATERSHED);
			int LOG_NB_STACKS = 3;
			int NB_STACKS = 1 << LOG_NB_STACKS;
			//var lvlStacks = rcNewArray<rcTempVector<LevelStackEntry>>(NB_STACKS, rcAllocHint.RC_ALLOC_PERM);
			var lvlStacks = new rcTempVector<LevelStackEntry>[NB_STACKS]; //类必须要new出来, 否则会导致GC移动对象导致null指针问题
			for (int i = 0; i < NB_STACKS; ++i)
			{
				lvlStacks[i] = new rcTempVector<LevelStackEntry>();
				lvlStacks[i].reserve(256);
			}
			var stack = new rcTempVector<LevelStackEntry>();
			stack.reserve(256);
			ushort* srcReg = buf;
			ushort* srcDist = buf + chf.spanCount;
			memset(srcReg, 0, sizeof(ushort) * chf.spanCount);
			memset(srcDist, 0, sizeof(ushort) * chf.spanCount);
			ushort regionId = 1;
			ushort level = (ushort)((chf.maxDistance + 1) & ~1);
			int expandIters = 8;
			if (borderSize > 0)
			{
				int bw = rcMin(w, borderSize);
				int bh = rcMin(h, borderSize);
				paintRectRegion(0, bw, 0, h, (ushort)(regionId | RC_BORDER_REG), ref chf, srcReg); regionId++;
				paintRectRegion(w - bw, w, 0, h, (ushort)(regionId | RC_BORDER_REG), ref chf, srcReg); regionId++;
				paintRectRegion(0, w, 0, bh, (ushort)(regionId | RC_BORDER_REG), ref chf, srcReg); regionId++;
				paintRectRegion(0, w, h - bh, h, (ushort)(regionId | RC_BORDER_REG), ref chf, srcReg); regionId++;
			}
			chf.borderSize = borderSize;
			int sId = -1;
			while (level > 0)
			{
				level = (ushort)(level >= 2 ? level - 2 : 0);
				sId = (sId + 1) & (NB_STACKS - 1);
				if (sId == 0)
					sortCellsByLevel(level, ref chf, srcReg, (uint)NB_STACKS, lvlStacks, 1);
				else
					appendStacks(ref lvlStacks[sId - 1], ref lvlStacks[sId], srcReg);
				{
					//rcScopedTimer timerExpand(ctx, RC_TIMER_BUILD_REGIONS_EXPAND);
					expandRegions(expandIters, level, ref chf, srcReg, srcDist, ref lvlStacks[sId], false);
				}
				{
					//rcScopedTimer timerFloor(ctx, RC_TIMER_BUILD_REGIONS_FLOOD);
					for (int j = 0; j < lvlStacks[sId].size(); j++)
					{
						LevelStackEntry current = lvlStacks[sId][j];
						int x = current.x;
						int y = current.y;
						int i = current.index;
						if (i >= 0 && srcReg[i] == 0)
						{
							if (floodRegion(x, y, i, level, regionId, ref chf, srcReg, srcDist, ref stack))
							{
								if (regionId == 0xFFFF)
								{
									//ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow");
									return false;
								}
								regionId++;
							}
						}
					}
				}
			}
			expandRegions(expandIters * 8, 0, ref chf, srcReg, srcDist, ref stack, true);
			////ctx->stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_WATERSHED);
			{
				//rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);
				rcIntArray overlaps = new rcIntArray();
				chf.maxRegions = regionId;
				if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, ref chf.maxRegions, ref chf, srcReg, ref overlaps))
					return false;
				if (overlaps.size() > 0)
				{
					//ctx->log(RC_LOG_ERROR, "rcBuildRegions: %d overlapping regions.", overlaps.size());
				}
			}
			for (int i = 0; i < chf.spanCount; ++i)
				chf.spans[i].reg = srcReg[i];
			return true;
		}
		public static bool rcBuildLayerRegions(rcContext* ctx, ref rcCompactHeightfield chf, int borderSize, int minRegionArea)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);
			int w = chf.width;
			int h = chf.height;
			ushort id = 1;
			rcScopedDelete<ushort> srcReg = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * chf.spanCount, rcAllocHint.RC_ALLOC_TEMP));
			if (null == srcReg)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildLayerRegions: Out of memory 'src' (%d).", chf.spanCount);
				return false;
			}
			memset(srcReg, 0, sizeof(ushort) * chf.spanCount);
			int nsweeps = rcMax(chf.width, chf.height);
			rcScopedDelete<rcSweepSpan> sweeps = new rcScopedDelete<rcSweepSpan>((rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan) * nsweeps, rcAllocHint.RC_ALLOC_TEMP));
			if (null == sweeps)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildLayerRegions: Out of memory 'sweeps' (%d).", nsweeps);
				return false;
			}
			if (borderSize > 0)
			{
				int bw = rcMin(w, borderSize);
				int bh = rcMin(h, borderSize);
				paintRectRegion(0, bw, 0, h, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
				paintRectRegion(w - bw, w, 0, h, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
				paintRectRegion(0, w, 0, bh, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
				paintRectRegion(0, w, h - bh, h, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
			}
			chf.borderSize = borderSize;
			rcIntArray prev = new rcIntArray(256);
			for (int y = borderSize; y < h - borderSize; ++y)
			{
				prev.resize(id + 1);
				memset(prev.m_impl.m_data, 0, sizeof(int) * id);
				ushort rid = 1;
				for (int x = borderSize; x < w - borderSize; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						if (chf.areas[i] == RC_null_AREA) continue;
						ushort previd = 0;
						if (rcGetCon(ref s, 0) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(0);
							int ay = y + rcGetDirOffsetY(0);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 0);
							if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
								previd = srcReg[ai];
						}
						if (0 == previd)
						{
							previd = rid++;
							sweeps[previd].rid = previd;
							sweeps[previd].ns = 0;
							sweeps[previd].nei = 0;
						}
						if (rcGetCon(ref s, 3) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(3);
							int ay = y + rcGetDirOffsetY(3);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 3);
							if (srcReg[ai] != 0 && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
							{
								ushort nr = srcReg[ai];
								if (0 == sweeps[previd].nei || sweeps[previd].nei == nr)
								{
									sweeps[previd].nei = nr;
									sweeps[previd].ns++;
									prev[nr]++;
								}
								else
								{
									sweeps[previd].nei = RC_null_NEI;
								}
							}
						}
						srcReg[i] = previd;
					}
				}
				for (int i = 1; i < rid; ++i)
				{
					if (sweeps[i].nei != RC_null_NEI && sweeps[i].nei != 0 &&
					prev[sweeps[i].nei] == (int)sweeps[i].ns)
					{
						sweeps[i].id = sweeps[i].nei;
					}
					else
					{
						sweeps[i].id = id++;
					}
				}
				for (int x = borderSize; x < w - borderSize; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						if (srcReg[i] > 0 && srcReg[i] < rid)
							srcReg[i] = sweeps[srcReg[i]].id;
					}
				}
			}
			{
				//rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);
				chf.maxRegions = id;
				if (!mergeAndFilterLayerRegions(ctx, minRegionArea, ref chf.maxRegions, ref chf, srcReg))
					return false;
			}
			for (int i = 0; i < chf.spanCount; ++i)
				chf.spans[i].reg = srcReg[i];
			return true;
		}
		public static bool rcBuildRegionsMonotone(rcContext* ctx, ref rcCompactHeightfield chf, int borderSize, int minRegionArea, int mergeRegionArea)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);
			int w = chf.width;
			int h = chf.height;
			ushort id = 1;
			rcScopedDelete<ushort> srcReg = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * chf.spanCount, rcAllocHint.RC_ALLOC_TEMP));
			if (null == srcReg)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount);
				return false;
			}
			memset(srcReg, 0, sizeof(ushort) * chf.spanCount);
			int nsweeps = rcMax(chf.width, chf.height);
			rcScopedDelete<rcSweepSpan> sweeps = new rcScopedDelete<rcSweepSpan>((rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan) * nsweeps, rcAllocHint.RC_ALLOC_TEMP));
			if (null == sweeps)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps);
				return false;
			}
			if (borderSize > 0)
			{
				int bw = rcMin(w, borderSize);
				int bh = rcMin(h, borderSize);
				paintRectRegion(0, bw, 0, h, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
				paintRectRegion(w - bw, w, 0, h, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
				paintRectRegion(0, w, 0, bh, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
				paintRectRegion(0, w, h - bh, h, (ushort)(id | RC_BORDER_REG), ref chf, srcReg); id++;
			}
			chf.borderSize = borderSize;
			rcIntArray prev = new rcIntArray(256);
			for (int y = borderSize; y < h - borderSize; ++y)
			{
				prev.resize(id + 1);
				memset(&prev.Data[0], 0, sizeof(int) * id);
				ushort rid = 1;
				for (int x = borderSize; x < w - borderSize; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						if (chf.areas[i] == RC_null_AREA) continue;
						ushort previd = 0;
						if (rcGetCon(ref s, 0) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(0);
							int ay = y + rcGetDirOffsetY(0);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 0);
							if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
								previd = srcReg[ai];
						}
						if (0 == previd)
						{
							previd = rid++;
							sweeps[previd].rid = previd;
							sweeps[previd].ns = 0;
							sweeps[previd].nei = 0;
						}
						if (rcGetCon(ref s, 3) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(3);
							int ay = y + rcGetDirOffsetY(3);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 3);
							if (srcReg[ai] != 0 && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
							{
								ushort nr = srcReg[ai];
								if (0 == sweeps[previd].nei || sweeps[previd].nei == nr)
								{
									sweeps[previd].nei = nr;
									sweeps[previd].ns++;
									prev[nr]++;
								}
								else
								{
									sweeps[previd].nei = RC_null_NEI;
								}
							}
						}
						srcReg[i] = previd;
					}
				}
				for (int i = 1; i < rid; ++i)
				{
					if (sweeps[i].nei != RC_null_NEI && sweeps[i].nei != 0 &&
					prev[sweeps[i].nei] == (int)sweeps[i].ns)
					{
						sweeps[i].id = sweeps[i].nei;
					}
					else
					{
						sweeps[i].id = id++;
					}
				}
				for (int x = borderSize; x < w - borderSize; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						if (srcReg[i] > 0 && srcReg[i] < rid)
							srcReg[i] = sweeps[srcReg[i]].id;
					}
				}
			}
			{
				//rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);
				rcIntArray overlaps = new rcIntArray();
				chf.maxRegions = id;
				if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, ref chf.maxRegions, ref chf, srcReg, ref overlaps))
					return false;
			}
			for (int i = 0; i < chf.spanCount; ++i)
				chf.spans[i].reg = srcReg[i];
			return true;
		}
		public static void rcSetCon(ref rcCompactSpan span, int direction, int neighborIndex)
		{
			int shift = direction * 6;
			uint con = span.con;
			span.con = (uint)((con & ~(0x3f << shift)) | (((uint)neighborIndex & 0x3f) << shift));
		}
		public static int rcGetCon(ref rcCompactSpan span, int direction)
		{
			int shift = direction * 6;
			return (int)((span.con >> shift) & 0x3f);
		}
		public static int rcGetDirOffsetX(int direction)
		{
			int* offset = stackalloc int[4] { -1, 0, 1, 0, };
			return offset[direction & 0x03];
		}
		public static int rcGetDirOffsetY(int direction)
		{
			int* offset = stackalloc int[4] { 0, 1, 0, -1 };
			return offset[direction & 0x03];
		}
		public static int rcGetDirForOffset(int offsetX, int offsetZ)
		{
			int* dirs = stackalloc int[5] { 3, 0, -1, 2, 1 };
			return dirs[((offsetZ + 1) << 1) + offsetX];
		}
		public static bool rcBuildHeightfieldLayers(rcContext* ctx, ref rcCompactHeightfield chf, int borderSize, int walkableHeight, ref rcHeightfieldLayerSet lset)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_LAYERS);
			int w = chf.width;
			int h = chf.height;
			rcScopedDelete<byte> srcReg = new rcScopedDelete<byte>((byte*)rcAlloc(sizeof(byte) * chf.spanCount, rcAllocHint.RC_ALLOC_TEMP));
			if (null == srcReg)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount);
				return false;
			}
			memset(srcReg, 0xff, sizeof(byte) * chf.spanCount);
			int nsweeps = chf.width;
			rcScopedDelete<rcLayerSweepSpan> sweeps = new rcScopedDelete<rcLayerSweepSpan>((rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan) * nsweeps, rcAllocHint.RC_ALLOC_TEMP));
			if (null == sweeps)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps);
				return false;
			}
			int* prevCount = stackalloc int[256];
			byte regId = 0;
			for (int y = borderSize; y < h - borderSize; ++y)
			{
				memset(prevCount, 0, sizeof(int) * regId);
				byte sweepId = 0;
				for (int x = borderSize; x < w - borderSize; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						if (chf.areas[i] == RC_null_AREA) continue;
						byte sid = 0xff;
						if (rcGetCon(ref s, 0) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(0);
							int ay = y + rcGetDirOffsetY(0);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 0);
							if (chf.areas[ai] != RC_null_AREA && srcReg[ai] != 0xff)
								sid = srcReg[ai];
						}
						if (sid == 0xff)
						{
							sid = sweepId++;
							sweeps[sid].nei = 0xff;
							sweeps[sid].ns = 0;
						}
						if (rcGetCon(ref s, 3) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(3);
							int ay = y + rcGetDirOffsetY(3);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 3);
							byte nr = srcReg[ai];
							if (nr != 0xff)
							{
								if (sweeps[sid].ns == 0)
									sweeps[sid].nei = nr;
								if (sweeps[sid].nei == nr)
								{
									sweeps[sid].ns++;
									prevCount[nr]++;
								}
								else
								{
									sweeps[sid].nei = 0xff;
								}
							}
						}
						srcReg[i] = sid;
					}
				}
				for (int i = 0; i < sweepId; ++i)
				{
					if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns)
					{
						sweeps[i].id = sweeps[i].nei;
					}
					else
					{
						if (regId == 255)
						{
							//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow.");
							return false;
						}
						sweeps[i].id = regId++;
					}
				}
				for (int x = borderSize; x < w - borderSize; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						if (srcReg[i] != 0xff)
							srcReg[i] = sweeps[srcReg[i]].id;
					}
				}
			}
			int nregs = (int)regId;
			rcScopedDelete<rcLayerRegion> regs = new rcScopedDelete<rcLayerRegion>((rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion) * nregs, rcAllocHint.RC_ALLOC_TEMP));
			if (null == regs)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs);
				return false;
			}
			memset(regs, 0, sizeof(rcLayerRegion) * nregs);
			for (int i = 0; i < nregs; ++i)
			{
				regs[i].layerId = 0xff;
				regs[i].ymin = 0xffff;
				regs[i].ymax = 0;
			}
			byte* lregs = stackalloc byte[RC_MAX_LAYERS];
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					//byte* lregs = stackalloc byte[RC_MAX_LAYERS];
					memset(lregs, 0, sizeof(byte) * RC_MAX_LAYERS);
					int nlregs = 0;
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						byte ri = srcReg[i];
						if (ri == 0xff) continue;
						regs[ri].ymin = rcMin(regs[ri].ymin, s.y);
						regs[ri].ymax = rcMax(regs[ri].ymax, s.y);
						if (nlregs < RC_MAX_LAYERS)
							lregs[nlregs++] = ri;
						for (int dir = 0; dir < 4; ++dir)
						{
							if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
							{
								int ax = x + rcGetDirOffsetX(dir);
								int ay = y + rcGetDirOffsetY(dir);
								int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
								byte rai = srcReg[ai];
								if (rai != 0xff && rai != ri)
								{
									fixed (byte* neis = regs[ri].neis)
										addUnique(neis, ref regs[ri].nneis, RC_MAX_NEIS, rai);
								}
							}
						}
					}
					for (int i = 0; i < nlregs - 1; ++i)
					{
						for (int j = i + 1; j < nlregs; ++j)
						{
							if (lregs[i] != lregs[j])
							{
								ref rcLayerRegion ri = ref regs[lregs[i]];
								ref rcLayerRegion rj = ref regs[lregs[j]];
								fixed (byte* layers = ri.layers)
								{
									fixed (byte* layers1 = rj.layers)
									{
										if (!addUnique(layers, ref ri.nlayers, RC_MAX_LAYERS, lregs[j]) ||
										!addUnique(layers1, ref rj.nlayers, RC_MAX_LAYERS, lregs[i]))
										{
											//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
											return false;
										}
									}
								}
							}
						}
					}
				}
			}
			byte layerId = 0;
			int MAX_STACK = 64;
			byte* stack = stackalloc byte[MAX_STACK];
			int nstack = 0;
			for (int i = 0; i < nregs; ++i)
			{
				ref rcLayerRegion root = ref regs[i];
				if (root.layerId != 0xff)
					continue;
				root.layerId = layerId;
				root.@base = 1;
				nstack = 0;
				stack[nstack++] = (byte)i;
				while (0 != nstack)
				{
					ref rcLayerRegion reg = ref regs[stack[0]];
					nstack--;
					for (int j = 0; j < nstack; ++j)
						stack[j] = stack[j + 1];
					int nneis = (int)reg.nneis;
					for (int j = 0; j < nneis; ++j)
					{
						byte nei = reg.neis[j];
						ref rcLayerRegion regn = ref regs[nei];
						if (regn.layerId != 0xff)
							continue;
						fixed (byte* layers = root.layers)
							if (contains(layers, root.nlayers, nei))
								continue;
						int ymin = rcMin(root.ymin, regn.ymin);
						int ymax = rcMax(root.ymax, regn.ymax);
						if ((ymax - ymin) >= 255)
							continue;
						if (nstack < MAX_STACK)
						{
							stack[nstack++] = (byte)nei;
							regn.layerId = layerId;
							for (int k = 0; k < regn.nlayers; ++k)
							{
								fixed (byte* layers = root.layers)
									if (!addUnique(layers, ref root.nlayers, RC_MAX_LAYERS, regn.layers[k]))
									{
										//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
										return false;
									}
							}
							root.ymin = rcMin(root.ymin, regn.ymin);
							root.ymax = rcMax(root.ymax, regn.ymax);
						}
					}
				}
				layerId++;
			}
			ushort mergeHeight = (ushort)(walkableHeight * 4);
			for (int i = 0; i < nregs; ++i)
			{
				ref rcLayerRegion ri = ref regs[i];
				if (0 == ri.@base) continue;
				byte newId = ri.layerId;
				for (; ; )
				{
					byte oldId = 0xff;
					for (int j = 0; j < nregs; ++j)
					{
						if (i == j) continue;
						ref rcLayerRegion rj = ref regs[j];
						if (0 == rj.@base) continue;
						if (!overlapRange(ri.ymin, (ushort)(ri.ymax + mergeHeight), rj.ymin, (ushort)(rj.ymax + mergeHeight)))
							continue;
						int ymin = rcMin(ri.ymin, rj.ymin);
						int ymax = rcMax(ri.ymax, rj.ymax);
						if ((ymax - ymin) >= 255)
							continue;

						bool overlap = false;
						for (int k = 0; k < nregs; ++k)
						{
							if (regs[k].layerId != rj.layerId)
								continue;
							fixed (byte* layers = ri.layers)
								if (contains(layers, ri.nlayers, (byte)k))
								{
									overlap = true;
									break;
								}
						}
						if (overlap)
							continue;
						oldId = rj.layerId;
						break;
					}
					if (oldId == 0xff)
						break;
					for (int j = 0; j < nregs; ++j)
					{
						ref rcLayerRegion rj = ref regs[j];
						if (rj.layerId == oldId)
						{
							rj.@base = 0;
							rj.layerId = newId;
							for (int k = 0; k < rj.nlayers; ++k)
							{
								fixed (byte* layers = ri.layers)
									if (!addUnique(layers, ref ri.nlayers, RC_MAX_LAYERS, rj.layers[k]))
									{
										//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
										return false;
									}
							}
							ri.ymin = rcMin(ri.ymin, rj.ymin);
							ri.ymax = rcMax(ri.ymax, rj.ymax);
						}
					}
				}
			}
			byte* remap = stackalloc byte[256];
			memset(remap, 0, 256);
			layerId = 0;
			for (int i = 0; i < nregs; ++i)
				remap[regs[i].layerId] = 1;
			for (int i = 0; i < 256; ++i)
			{
				if (0 != remap[i])
					remap[i] = layerId++;
				else
					remap[i] = 0xff;
			}
			for (int i = 0; i < nregs; ++i)
				regs[i].layerId = remap[regs[i].layerId];
			if (layerId == 0)
				return true;
			rcAssert(lset.layers == null);
			int lw = w - borderSize * 2;
			int lh = h - borderSize * 2;
			float* bmin = stackalloc float[3], bmax = stackalloc float[3];
			rcCompactHeightfield* chf_ptr = (rcCompactHeightfield*)Unsafe.AsPointer(ref chf);
			rcVcopy(bmin, chf_ptr->bmin);
			rcVcopy(bmax, chf_ptr->bmax);
			bmin[0] += borderSize * chf.cs;
			bmin[2] += borderSize * chf.cs;
			bmax[0] -= borderSize * chf.cs;
			bmax[2] -= borderSize * chf.cs;
			lset.nlayers = (int)layerId;
			lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer) * lset.nlayers, rcAllocHint.RC_ALLOC_PERM);
			if (null == lset.layers)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers);
				return false;
			}
			memset(lset.layers, 0, sizeof(rcHeightfieldLayer) * lset.nlayers);
			for (int i = 0; i < lset.nlayers; ++i)
			{
				byte curId = (byte)i;
				rcHeightfieldLayer* layer = &lset.layers[i];
				int gridSize = sizeof(byte) * lw * lh;
				layer->heights = (byte*)rcAlloc(gridSize, rcAllocHint.RC_ALLOC_PERM);
				if (null == layer->heights)
				{
					//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize);
					return false;
				}
				memset(layer->heights, 0xff, gridSize);
				layer->areas = (byte*)rcAlloc(gridSize, rcAllocHint.RC_ALLOC_PERM);
				if (null == layer->areas)
				{
					//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize);
					return false;
				}
				memset(layer->areas, 0, gridSize);
				layer->cons = (byte*)rcAlloc(gridSize, rcAllocHint.RC_ALLOC_PERM);
				if (null == layer->cons)
				{
					//ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize);
					return false;
				}
				memset(layer->cons, 0, gridSize);
				int hmin = 0, hmax = 0;
				for (int j = 0; j < nregs; ++j)
				{
					if (regs[j].@base != 0 && regs[j].layerId == curId)
					{
						hmin = (int)regs[j].ymin;
						hmax = (int)regs[j].ymax;
					}
				}
				layer->width = lw;
				layer->height = lh;
				layer->cs = chf.cs;
				layer->ch = chf.ch;
				rcVcopy(layer->bmin, bmin);
				rcVcopy(layer->bmax, bmax);
				layer->bmin[1] = bmin[1] + hmin * chf.ch;
				layer->bmax[1] = bmin[1] + hmax * chf.ch;
				layer->hmin = hmin;
				layer->hmax = hmax;
				layer->minx = layer->width;
				layer->maxx = 0;
				layer->miny = layer->height;
				layer->maxy = 0;
				for (int y = 0; y < lh; ++y)
				{
					for (int x = 0; x < lw; ++x)
					{
						int cx = borderSize + x;
						int cy = borderSize + y;
						ref rcCompactCell c = ref chf.cells[cx + cy * w];
						for (int j = (int)c.index, nj = (int)(c.index + c.count); j < nj; ++j)
						{
							ref rcCompactSpan s = ref chf.spans[j];
							if (srcReg[j] == 0xff)
								continue;
							byte lid = regs[srcReg[j]].layerId;
							if (lid != curId)
								continue;
							layer->minx = rcMin(layer->minx, x);
							layer->maxx = rcMax(layer->maxx, x);
							layer->miny = rcMin(layer->miny, y);
							layer->maxy = rcMax(layer->maxy, y);
							int idx = x + y * lw;
							layer->heights[idx] = (byte)(s.y - hmin);
							layer->areas[idx] = chf.areas[j];
							byte portal = 0;
							byte con = 0;
							for (int dir = 0; dir < 4; ++dir)
							{
								if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
								{
									int ax = cx + rcGetDirOffsetX(dir);
									int ay = cy + rcGetDirOffsetY(dir);
									int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
									byte alid = (byte)(srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff);
									if (chf.areas[ai] != RC_null_AREA && lid != alid)
									{
										portal |= (byte)(1 << dir);
										ref rcCompactSpan as1 = ref chf.spans[ai];
										if (as1.y > hmin)
											layer->heights[idx] = rcMax(layer->heights[idx], (byte)(as1.y - hmin));
									}
									if (chf.areas[ai] != RC_null_AREA && lid == alid)
									{
										int nx = ax - borderSize;
										int ny = ay - borderSize;
										if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
											con |= (byte)(1 << dir);
									}
								}
							}
							layer->cons[idx] = (byte)((portal << 4) | con);
						}
					}
				}
				if (layer->minx > layer->maxx)
					layer->minx = layer->maxx = 0;
				if (layer->miny > layer->maxy)
					layer->miny = layer->maxy = 0;
			}
			return true;
		}
		public static bool rcBuildPolyMesh(rcContext* ctx, ref rcContourSet cset, int nvp, ref rcPolyMesh mesh)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_POLYMESH);
			rcPolyMesh* mesh_ptr = (rcPolyMesh*)Unsafe.AsPointer(ref mesh);
			rcContourSet* cset_ptr = (rcContourSet*)Unsafe.AsPointer(ref cset);
			rcVcopy(mesh_ptr->bmin, cset_ptr->bmin);
			rcVcopy(mesh_ptr->bmax, cset_ptr->bmax);
			mesh.cs = cset.cs;
			mesh.ch = cset.ch;
			mesh.borderSize = cset.borderSize;
			mesh.maxEdgeError = cset.maxError;
			int maxVertices = 0;
			int maxTris = 0;
			int maxVertsPerCont = 0;
			for (int i = 0; i < cset.nconts; ++i)
			{
				if (cset.conts[i].nverts < 3) continue;
				maxVertices += cset.conts[i].nverts;
				maxTris += cset.conts[i].nverts - 2;
				maxVertsPerCont = rcMax(maxVertsPerCont, cset.conts[i].nverts);
			}
			if (maxVertices >= 0xfffe)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices);
				return false;
			}
			rcScopedDelete<byte> vflags = new rcScopedDelete<byte>((byte*)rcAlloc(sizeof(byte) * maxVertices, rcAllocHint.RC_ALLOC_TEMP));
			if (null == vflags)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'vflags' (%d).", maxVertices);
				return false;
			}
			memset(vflags, 0, maxVertices);
			mesh.verts = (ushort*)rcAlloc(sizeof(ushort) * maxVertices * 3, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.verts)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices);
				return false;
			}
			mesh.polys = (ushort*)rcAlloc(sizeof(ushort) * maxTris * nvp * 2, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.polys)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris * nvp * 2);
				return false;
			}
			mesh.regs = (ushort*)rcAlloc(sizeof(ushort) * maxTris, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.regs)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' (%d).", maxTris);
				return false;
			}
			mesh.areas = (byte*)rcAlloc(sizeof(byte) * maxTris, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.areas)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' (%d).", maxTris);
				return false;
			}
			mesh.nverts = 0;
			mesh.npolys = 0;
			mesh.nvp = nvp;
			mesh.maxpolys = maxTris;
			memset(mesh.verts, 0, sizeof(ushort) * maxVertices * 3);
			memset(mesh.polys, 0xff, sizeof(ushort) * maxTris * nvp * 2);
			memset(mesh.regs, 0, sizeof(ushort) * maxTris);
			memset(mesh.areas, 0, sizeof(byte) * maxTris);
			rcScopedDelete<int> nextVert = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * maxVertices, rcAllocHint.RC_ALLOC_TEMP));
			if (null == nextVert)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices);
				return false;
			}
			memset(nextVert, 0, sizeof(int) * maxVertices);
			rcScopedDelete<int> firstVert = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * VERTEX_BUCKET_COUNT, rcAllocHint.RC_ALLOC_TEMP));
			if (null == firstVert)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT);
				return false;
			}
			for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i)
				firstVert[i] = -1;
			rcScopedDelete<int> indices = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * maxVertsPerCont, rcAllocHint.RC_ALLOC_TEMP));
			if (null == indices)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont);
				return false;
			}
			rcScopedDelete<int> tris = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * maxVertsPerCont * 3, rcAllocHint.RC_ALLOC_TEMP));
			if (null == tris)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont * 3);
				return false;
			}
			rcScopedDelete<ushort> polys = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * (maxVertsPerCont + 1) * nvp, rcAllocHint.RC_ALLOC_TEMP));
			if (null == polys)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont * nvp);
				return false;
			}
			ushort* tmpPoly = &polys.Data[maxVertsPerCont * nvp];
			for (int i = 0; i < cset.nconts; ++i)
			{
				ref rcContour cont = ref cset.conts[i];
				if (cont.nverts < 3)
					continue;
				for (int j = 0; j < cont.nverts; ++j)
					indices[j] = j;
				int ntris = triangulate(cont.nverts, cont.verts, &indices.Data[0], &tris.Data[0]);
				if (ntris <= 0)
				{
					//ctx->log(RC_LOG_WARNING, "rcBuildPolyMesh: Bad triangulation Contour %d.", i);
					ntris = -ntris;
				}
				for (int j = 0; j < cont.nverts; ++j)
				{
					int* v = &cont.verts[j * 4];
					indices[j] = addVertex((ushort)v[0], (ushort)v[1], (ushort)v[2],
					 mesh.verts, firstVert, nextVert, ref mesh.nverts);
					if (0 != (v[3] & RC_BORDER_VERTEX))
					{
						vflags[indices[j]] = 1;
					}
				}
				int npolys = 0;
				memset(polys, 0xff, maxVertsPerCont * nvp * sizeof(ushort));
				for (int j = 0; j < ntris; ++j)
				{
					int* t = &tris.Data[j * 3];
					if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
					{
						polys[npolys * nvp + 0] = (ushort)indices[t[0]];
						polys[npolys * nvp + 1] = (ushort)indices[t[1]];
						polys[npolys * nvp + 2] = (ushort)indices[t[2]];
						npolys++;
					}
				}
				if (0 == npolys)
					continue;
				if (nvp > 3)
				{
					for (; ; )
					{
						int bestMergeVal = 0;
						int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
						for (int j = 0; j < npolys - 1; ++j)
						{
							ushort* pj = &polys.Data[j * nvp];
							for (int k = j + 1; k < npolys; ++k)
							{
								ushort* pk = &polys.Data[k * nvp];
								int ea = 0, eb = 0;
								int v = getPolyMergeValue(pj, pk, mesh.verts, ref ea, ref eb, nvp);
								if (v > bestMergeVal)
								{
									bestMergeVal = v;
									bestPa = j;
									bestPb = k;
									bestEa = ea;
									bestEb = eb;
								}
							}
						}
						if (bestMergeVal > 0)
						{
							ushort* pa = &polys.Data[bestPa * nvp];
							ushort* pb = &polys.Data[bestPb * nvp];
							mergePolyVerts(pa, pb, bestEa, bestEb, tmpPoly, nvp);
							ushort* lastPoly = polys.Data + ((npolys - 1) * nvp);
							if (pb != lastPoly)
								memcpy(pb, lastPoly, sizeof(ushort) * nvp);
							npolys--;
						}
						else
						{
							break;
						}
					}
				}
				for (int j = 0; j < npolys; ++j)
				{
					ushort* p = &mesh.polys[mesh.npolys * nvp * 2];
					ushort* q = &polys.Data[j * nvp];
					for (int k = 0; k < nvp; ++k)
						p[k] = q[k];
					mesh.regs[mesh.npolys] = cont.reg;
					mesh.areas[mesh.npolys] = cont.area;
					mesh.npolys++;
					if (mesh.npolys > maxTris)
					{
						//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many polygons %d (max:%d).", mesh.npolys, maxTris);
						return false;
					}
				}
			}
			for (int i = 0; i < mesh.nverts; ++i)
			{
				if (0 != vflags[i])
				{
					if (!canRemoveVertex(ctx, ref mesh, (ushort)i))
						continue;
					if (!removeVertex(ctx, ref mesh, (ushort)i, maxTris))
					{
						//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex %d.", i);
						return false;
					}
					for (int j = i; j < mesh.nverts; ++j)
						vflags[j] = vflags[j + 1];
					--i;
				}
			}
			if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp))
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed.");
				return false;
			}
			if (mesh.borderSize > 0)
			{
				int w = cset.width;
				int h = cset.height;
				for (int i = 0; i < mesh.npolys; ++i)
				{
					ushort* p = &mesh.polys[i * 2 * nvp];
					for (int j = 0; j < nvp; ++j)
					{
						if (p[j] == RC_MESH_NULL_IDX) break;
						if (p[nvp + j] != RC_MESH_NULL_IDX)
							continue;
						int nj = j + 1;
						if (nj >= nvp || p[nj] == RC_MESH_NULL_IDX) nj = 0;
						ushort* va = &mesh.verts[p[j] * 3];
						ushort* vb = &mesh.verts[p[nj] * 3];
						if ((int)va[0] == 0 && (int)vb[0] == 0)
							p[nvp + j] = 0x8000 | 0;
						else if ((int)va[2] == h && (int)vb[2] == h)
							p[nvp + j] = 0x8000 | 1;
						else if ((int)va[0] == w && (int)vb[0] == w)
							p[nvp + j] = 0x8000 | 2;
						else if ((int)va[2] == 0 && (int)vb[2] == 0)
							p[nvp + j] = 0x8000 | 3;
					}
				}
			}
			mesh.flags = (ushort*)rcAlloc(sizeof(ushort) * mesh.npolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.flags)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.flags' (%d).", mesh.npolys);
				return false;
			}
			memset(mesh.flags, 0, sizeof(ushort) * mesh.npolys);
			if (mesh.nverts > 0xffff)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff);
			}
			if (mesh.npolys > 0xffff)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff);
			}
			return true;
		}
		public static bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, int nmeshes, ref rcPolyMesh mesh)
		{
			rcAssert(ctx);
			if (0 == nmeshes || null == meshes)
				return true;
			//rcScopedTimer timer(ctx, RC_TIMER_MERGE_POLYMESH);
			mesh.nvp = meshes[0]->nvp;
			mesh.cs = meshes[0]->cs;
			mesh.ch = meshes[0]->ch;
			rcPolyMesh* mesh_ptr = (rcPolyMesh*)Unsafe.AsPointer(ref mesh);
			rcVcopy(mesh_ptr->bmin, meshes[0]->bmin);
			rcVcopy(mesh_ptr->bmax, meshes[0]->bmax);
			int maxVerts = 0;
			int maxPolys = 0;
			int maxVertsPerMesh = 0;
			for (int i = 0; i < nmeshes; ++i)
			{
				rcVmin(mesh_ptr->bmin, meshes[i]->bmin);
				rcVmax(mesh_ptr->bmax, meshes[i]->bmax);
				maxVertsPerMesh = rcMax(maxVertsPerMesh, meshes[i]->nverts);
				maxVerts += meshes[i]->nverts;
				maxPolys += meshes[i]->npolys;
			}
			mesh.nverts = 0;
			mesh.verts = (ushort*)rcAlloc(sizeof(ushort) * maxVerts * 3, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.verts)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts * 3);
				return false;
			}
			mesh.npolys = 0;
			mesh.polys = (ushort*)rcAlloc(sizeof(ushort) * maxPolys * 2 * mesh.nvp, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.polys)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' (%d).", maxPolys * 2 * mesh.nvp);
				return false;
			}
			memset(mesh.polys, 0xff, sizeof(ushort) * maxPolys * 2 * mesh.nvp);
			mesh.regs = (ushort*)rcAlloc(sizeof(ushort) * maxPolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.regs)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' (%d).", maxPolys);
				return false;
			}
			memset(mesh.regs, 0, sizeof(ushort) * maxPolys);
			mesh.areas = (byte*)rcAlloc(sizeof(byte) * maxPolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.areas)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' (%d).", maxPolys);
				return false;
			}
			memset(mesh.areas, 0, sizeof(byte) * maxPolys);
			mesh.flags = (ushort*)rcAlloc(sizeof(ushort) * maxPolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.flags)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' (%d).", maxPolys);
				return false;
			}
			memset(mesh.flags, 0, sizeof(ushort) * maxPolys);
			rcScopedDelete<int> nextVert = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * maxVerts, rcAllocHint.RC_ALLOC_TEMP));
			if (null == nextVert)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts);
				return false;
			}
			memset(nextVert, 0, sizeof(int) * maxVerts);
			rcScopedDelete<int> firstVert = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * VERTEX_BUCKET_COUNT, rcAllocHint.RC_ALLOC_TEMP));
			if (null == firstVert)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT);
				return false;
			}
			for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i)
				firstVert[i] = -1;
			rcScopedDelete<ushort> vremap = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * maxVertsPerMesh, rcAllocHint.RC_ALLOC_PERM));
			if (null == vremap)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh);
				return false;
			}
			memset(vremap, 0, sizeof(ushort) * maxVertsPerMesh);
			for (int i = 0; i < nmeshes; ++i)
			{
				rcPolyMesh* pmesh = meshes[i];
				ushort ox = (ushort)floorf((pmesh->bmin[0] - mesh.bmin[0]) / mesh.cs + 0.5f);
				ushort oz = (ushort)floorf((pmesh->bmin[2] - mesh.bmin[2]) / mesh.cs + 0.5f);
				bool isMinX = (ox == 0);
				bool isMinZ = (oz == 0);
				bool isMaxX = ((ushort)floorf((mesh.bmax[0] - pmesh->bmax[0]) / mesh.cs + 0.5f)) == 0;
				bool isMaxZ = ((ushort)floorf((mesh.bmax[2] - pmesh->bmax[2]) / mesh.cs + 0.5f)) == 0;
				bool isOnBorder = (isMinX || isMinZ || isMaxX || isMaxZ);
				for (int j = 0; j < pmesh->nverts; ++j)
				{
					ushort* v = &pmesh->verts[j * 3];
					vremap[j] = addVertex((ushort)(v[0] + ox), v[1], (ushort)(v[2] + oz),
					 mesh.verts, firstVert, nextVert, ref mesh.nverts);
				}
				for (int j = 0; j < pmesh->npolys; ++j)
				{
					ushort* tgt = &mesh.polys[mesh.npolys * 2 * mesh.nvp];
					ushort* src = &pmesh->polys[j * 2 * mesh.nvp];
					mesh.regs[mesh.npolys] = pmesh->regs[j];
					mesh.areas[mesh.npolys] = pmesh->areas[j];
					mesh.flags[mesh.npolys] = pmesh->flags[j];
					mesh.npolys++;
					for (int k = 0; k < mesh.nvp; ++k)
					{
						if (src[k] == RC_MESH_NULL_IDX) break;
						tgt[k] = vremap[src[k]];
					}
					if (isOnBorder)
					{
						for (int k = mesh.nvp; k < mesh.nvp * 2; ++k)
						{
							if ((src[k] & 0x8000) != 0 && src[k] != 0xffff)
							{
								ushort dir = (ushort)(src[k] & 0xf);
								switch (dir)
								{
									case 0:
										if (isMinX)
											tgt[k] = src[k];
										break;
									case 1:
										if (isMaxZ)
											tgt[k] = src[k];
										break;
									case 2:
										if (isMaxX)
											tgt[k] = src[k];
										break;
									case 3:
										if (isMinZ)
											tgt[k] = src[k];
										break;
								}
							}
						}
					}
				}
			}
			if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp))
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed.");
				return false;
			}
			if (mesh.nverts > 0xffff)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff);
			}
			if (mesh.npolys > 0xffff)
			{
				//ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff);
			}
			return true;
		}
		public static bool rcBuildPolyMeshDetail(rcContext* ctx, ref rcPolyMesh mesh, ref rcCompactHeightfield chf, float sampleDist, float sampleMaxError, ref rcPolyMeshDetail dmesh)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_POLYMESHDETAIL);
			if (mesh.nverts == 0 || mesh.npolys == 0)
				return true;
			int nvp = mesh.nvp;
			float cs = mesh.cs;
			float ch = mesh.ch;
			rcPolyMesh* mesh_ptr = (rcPolyMesh*)Unsafe.AsPointer(ref mesh);
			float* orig = mesh_ptr->bmin;
			int borderSize = mesh.borderSize;
			int heightSearchRadius = rcMax(1, (int)ceilf(mesh.maxEdgeError));
			rcIntArray edges = new rcIntArray(64);
			rcIntArray tris = new rcIntArray(512);
			rcIntArray arr = new rcIntArray(512);
			rcIntArray samples = new rcIntArray(512);
			float* verts = stackalloc float[256 * 3];
			rcHeightPatch hp = new rcHeightPatch();
			int nPolyVerts = 0;
			int maxhw = 0, maxhh = 0;
			rcScopedDelete<int> bounds = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * mesh.npolys * 4, rcAllocHint.RC_ALLOC_TEMP));
			if (null == bounds)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys * 4);
				return false;
			}
			rcScopedDelete<float> poly = new rcScopedDelete<float>((float*)rcAlloc(sizeof(float) * nvp * 3, rcAllocHint.RC_ALLOC_TEMP));
			if (null == poly)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp * 3);
				return false;
			}
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				ref int xmin = ref bounds[i * 4 + 0];
				ref int xmax = ref bounds[i * 4 + 1];
				ref int ymin = ref bounds[i * 4 + 2];
				ref int ymax = ref bounds[i * 4 + 3];
				xmin = chf.width;
				xmax = 0;
				ymin = chf.height;
				ymax = 0;
				for (int j = 0; j < nvp; ++j)
				{
					if (p[j] == RC_MESH_NULL_IDX) break;
					ushort* v = &mesh.verts[p[j] * 3];
					xmin = rcMin(xmin, v[0]);
					xmax = rcMax(xmax, v[0]);
					ymin = rcMin(ymin, v[2]);
					ymax = rcMax(ymax, v[2]);
					nPolyVerts++;
				}
				xmin = rcMax(0, xmin - 1);
				xmax = rcMin(chf.width, xmax + 1);
				ymin = rcMax(0, ymin - 1);
				ymax = rcMin(chf.height, ymax + 1);
				if (xmin >= xmax || ymin >= ymax) continue;
				maxhw = rcMax(maxhw, xmax - xmin);
				maxhh = rcMax(maxhh, ymax - ymin);
			}
			hp.data = (ushort*)rcAlloc(sizeof(ushort) * maxhw * maxhh, rcAllocHint.RC_ALLOC_TEMP);
			if (null == hp.data)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw * maxhh);
				return false;
			}
			dmesh.nmeshes = mesh.npolys;
			dmesh.nverts = 0;
			dmesh.ntris = 0;
			dmesh.meshes = (uint*)rcAlloc(sizeof(uint) * dmesh.nmeshes * 4, rcAllocHint.RC_ALLOC_PERM);
			if (null == dmesh.meshes)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes * 4);
				return false;
			}
			int vcap = nPolyVerts + nPolyVerts / 2;
			int tcap = vcap * 2;
			dmesh.nverts = 0;
			dmesh.verts = (float*)rcAlloc(sizeof(float) * vcap * 3, rcAllocHint.RC_ALLOC_PERM);
			if (null == dmesh.verts)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap * 3);
				return false;
			}
			dmesh.ntris = 0;
			dmesh.tris = (byte*)rcAlloc(sizeof(byte) * tcap * 4, rcAllocHint.RC_ALLOC_PERM);
			if (null == dmesh.tris)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap * 4);
				return false;
			}
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				int npoly = 0;
				for (int j = 0; j < nvp; ++j)
				{
					if (p[j] == RC_MESH_NULL_IDX) break;
					ushort* v = &mesh.verts[p[j] * 3];
					poly[j * 3 + 0] = v[0] * cs;
					poly[j * 3 + 1] = v[1] * ch;
					poly[j * 3 + 2] = v[2] * cs;
					npoly++;
				}
				hp.xmin = bounds[i * 4 + 0];
				hp.ymin = bounds[i * 4 + 2];
				hp.width = bounds[i * 4 + 1] - bounds[i * 4 + 0];
				hp.height = bounds[i * 4 + 3] - bounds[i * 4 + 2];
				getHeightData(ctx, ref chf, p, npoly, mesh.verts, borderSize, ref hp, ref arr, mesh.regs[i]);
				int nverts = 0;
				if (!buildPolyDetail(ctx, poly, npoly,
				 sampleDist, sampleMaxError,
				 heightSearchRadius, ref chf, ref hp,
				 verts, ref nverts, ref tris,
				 ref edges, ref samples))
				{
					return false;
				}
				for (int j = 0; j < nverts; ++j)
				{
					verts[j * 3 + 0] += orig[0];
					verts[j * 3 + 1] += orig[1] + chf.ch;
					verts[j * 3 + 2] += orig[2];
				}
				for (int j = 0; j < npoly; ++j)
				{
					poly[j * 3 + 0] += orig[0];
					poly[j * 3 + 1] += orig[1];
					poly[j * 3 + 2] += orig[2];
				}
				int ntris = tris.size() / 4;
				dmesh.meshes[i * 4 + 0] = (uint)dmesh.nverts;
				dmesh.meshes[i * 4 + 1] = (uint)nverts;
				dmesh.meshes[i * 4 + 2] = (uint)dmesh.ntris;
				dmesh.meshes[i * 4 + 3] = (uint)ntris;
				if (dmesh.nverts + nverts > vcap)
				{
					while (dmesh.nverts + nverts > vcap)
						vcap += 256;
					float* newv = (float*)rcAlloc(sizeof(float) * vcap * 3, rcAllocHint.RC_ALLOC_PERM);
					if (null == newv)
					{
						//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap * 3);
						return false;
					}
					if (0 != dmesh.nverts)
						memcpy(newv, dmesh.verts, sizeof(float) * 3 * dmesh.nverts);
					rcFree(dmesh.verts);
					dmesh.verts = newv;
				}
				for (int j = 0; j < nverts; ++j)
				{
					dmesh.verts[dmesh.nverts * 3 + 0] = verts[j * 3 + 0];
					dmesh.verts[dmesh.nverts * 3 + 1] = verts[j * 3 + 1];
					dmesh.verts[dmesh.nverts * 3 + 2] = verts[j * 3 + 2];
					dmesh.nverts++;
				}
				if (dmesh.ntris + ntris > tcap)
				{
					while (dmesh.ntris + ntris > tcap)
						tcap += 256;
					byte* newt = (byte*)rcAlloc(sizeof(byte) * tcap * 4, rcAllocHint.RC_ALLOC_PERM);
					if (null == newt)
					{
						//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap * 4);
						return false;
					}
					if (0 != dmesh.ntris)
						memcpy(newt, dmesh.tris, sizeof(byte) * 4 * dmesh.ntris);
					rcFree(dmesh.tris);
					dmesh.tris = newt;
				}
				for (int j = 0; j < ntris; ++j)
				{
					int* t = tris.Data + (j * 4);
					dmesh.tris[dmesh.ntris * 4 + 0] = (byte)t[0];
					dmesh.tris[dmesh.ntris * 4 + 1] = (byte)t[1];
					dmesh.tris[dmesh.ntris * 4 + 2] = (byte)t[2];
					dmesh.tris[dmesh.ntris * 4 + 3] = getTriFlags(&verts[t[0] * 3], &verts[t[1] * 3], &verts[t[2] * 3], poly, npoly);
					dmesh.ntris++;
				}
			}
			hp.Dispose();
			return true;
		}
		public static bool rcCopyPolyMesh(rcContext* ctx, ref rcPolyMesh src, ref rcPolyMesh dst)
		{
			rcAssert(ctx);
			rcAssert(dst.verts == null);
			rcAssert(dst.polys == null);
			rcAssert(dst.regs == null);
			rcAssert(dst.areas == null);
			rcAssert(dst.flags == null);
			dst.nverts = src.nverts;
			dst.npolys = src.npolys;
			dst.maxpolys = src.npolys;
			dst.nvp = src.nvp;
			rcPolyMesh* dst_ptr = (rcPolyMesh*)Unsafe.AsPointer(ref dst);
			rcPolyMesh* src_ptr = (rcPolyMesh*)Unsafe.AsPointer(ref src);
			rcVcopy(dst_ptr->bmin, src_ptr->bmin);
			rcVcopy(dst_ptr->bmax, src_ptr->bmax);
			dst.cs = src.cs;
			dst.ch = src.ch;
			dst.borderSize = src.borderSize;
			dst.maxEdgeError = src.maxEdgeError;
			dst.verts = (ushort*)rcAlloc(sizeof(ushort) * src.nverts * 3, rcAllocHint.RC_ALLOC_PERM);
			if (null == dst.verts)
			{
				//ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.verts' (%d).", src.nverts * 3);
				return false;
			}
			memcpy(dst.verts, src.verts, sizeof(ushort) * src.nverts * 3);
			dst.polys = (ushort*)rcAlloc(sizeof(ushort) * src.npolys * 2 * src.nvp, rcAllocHint.RC_ALLOC_PERM);
			if (null == dst.polys)
			{
				//ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.polys' (%d).", src.npolys * 2 * src.nvp);
				return false;
			}
			memcpy(dst.polys, src.polys, sizeof(ushort) * src.npolys * 2 * src.nvp);
			dst.regs = (ushort*)rcAlloc(sizeof(ushort) * src.npolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == dst.regs)
			{
				//ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.regs' (%d).", src.npolys);
				return false;
			}
			memcpy(dst.regs, src.regs, sizeof(ushort) * src.npolys);
			dst.areas = (byte*)rcAlloc(sizeof(byte) * src.npolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == dst.areas)
			{
				//ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.areas' (%d).", src.npolys);
				return false;
			}
			memcpy(dst.areas, src.areas, sizeof(byte) * src.npolys);
			dst.flags = (ushort*)rcAlloc(sizeof(ushort) * src.npolys, rcAllocHint.RC_ALLOC_PERM);
			if (null == dst.flags)
			{
				//ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.flags' (%d).", src.npolys);
				return false;
			}
			memcpy(dst.flags, src.flags, sizeof(ushort) * src.npolys);
			return true;
		}
		public static bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, int nmeshes, ref rcPolyMeshDetail mesh)
		{
			rcAssert(ctx);
			//rcScopedTimer timer(ctx, RC_TIMER_MERGE_POLYMESHDETAIL);
			int maxVerts = 0;
			int maxTris = 0;
			int maxMeshes = 0;
			for (int i = 0; i < nmeshes; ++i)
			{
				if (null == meshes[i]) continue;
				maxVerts += meshes[i]->nverts;
				maxTris += meshes[i]->ntris;
				maxMeshes += meshes[i]->nmeshes;
			}
			mesh.nmeshes = 0;
			mesh.meshes = (uint*)rcAlloc(sizeof(uint) * maxMeshes * 4, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.meshes)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes * 4);
				return false;
			}
			mesh.ntris = 0;
			mesh.tris = (byte*)rcAlloc(sizeof(byte) * maxTris * 4, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.tris)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris * 4);
				return false;
			}
			mesh.nverts = 0;
			mesh.verts = (float*)rcAlloc(sizeof(float) * maxVerts * 3, rcAllocHint.RC_ALLOC_PERM);
			if (null == mesh.verts)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts * 3);
				return false;
			}
			for (int i = 0; i < nmeshes; ++i)
			{
				rcPolyMeshDetail* dm = meshes[i];
				if (null == dm) continue;
				for (int j = 0; j < dm->nmeshes; ++j)
				{
					uint* dst = &mesh.meshes[mesh.nmeshes * 4];
					uint* src = &dm->meshes[j * 4];
					dst[0] = (uint)mesh.nverts + src[0];
					dst[1] = src[1];
					dst[2] = (uint)mesh.ntris + src[2];
					dst[3] = src[3];
					mesh.nmeshes++;
				}
				for (int k = 0; k < dm->nverts; ++k)
				{
					rcVcopy(&mesh.verts[mesh.nverts * 3], &dm->verts[k * 3]);
					mesh.nverts++;
				}
				for (int k = 0; k < dm->ntris; ++k)
				{
					mesh.tris[mesh.ntris * 4 + 0] = dm->tris[k * 4 + 0];
					mesh.tris[mesh.ntris * 4 + 1] = dm->tris[k * 4 + 1];
					mesh.tris[mesh.ntris * 4 + 2] = dm->tris[k * 4 + 2];
					mesh.tris[mesh.ntris * 4 + 3] = dm->tris[k * 4 + 3];
					mesh.ntris++;
				}
			}
			return true;
		}
		public delegate void* rcAllocFunc(int size, rcAllocHint hint);

		public delegate void rcFreeFunc(void* ptr);

		public static void* rcAlloc(int size, rcAllocHint hint)
		{
			return sRecastAllocFunc(size, hint);
		}
		public static void rcFree(void* ptr)
		{
			if (ptr != null)
			{
				sRecastFreeFunc(ptr);
			}
		}
		public delegate void rcAssertFailFunc(char* expression, char* file, int line);

		public static void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc)
		{
			sRecastAssertFailFunc = assertFailFunc;
		}
		public static rcAssertFailFunc* rcAssertFailGetCustom()
		{
			return sRecastAssertFailFunc;
		}
		public static T* rcNew<T>(rcAllocHint allocLifetime) where T : new()
		{
			T* ptr = (T*)rcAlloc(sizeof(T), allocLifetime);
			*ptr = new T();
			return ptr;
		}
		public static T* rcNewArray<T>(int count, rcAllocHint allocLifetime) where T : new()
		{
			T* ptr = (T*)rcAlloc(sizeof(T) * count, allocLifetime);
			for (int i = 0; i < count; i++)
				ptr[i] = new T();
			return ptr;
		}
		public static void rcDelete<T>(T* ptr)
		{
			if (null != ptr)
			{
				//ptr->~T();
				if (*ptr is IDisposable disposable)
					disposable.Dispose();
				rcFree(ptr);
			}
		}
		public static void calcTriNormal(float* v0, float* v1, float* v2, float* faceNormal)
		{
			float* e0 = stackalloc float[3], e1 = stackalloc float[3];
			rcVsub(e0, v1, v0);
			rcVsub(e1, v2, v0);
			rcVcross(faceNormal, e0, e1);
			rcVnormalize(faceNormal);
		}
		public static void* rcAllocDefault(int size, rcAllocHint @rcAllocHint)
		{
			return malloc(size);
		}
		public static void rcFreeDefault(void* ptr)
		{
			free(ptr);
		}
		public static void rcAllocSetCustom(rcAllocFunc allocFunc, rcFreeFunc freeFunc)
		{
			sRecastAllocFunc = allocFunc != null ? allocFunc : rcAllocDefault;
			sRecastFreeFunc = freeFunc != null ? freeFunc : rcFreeDefault;
		}
		public static void insertSort(byte* data, int dataLength)
		{
			for (int valueIndex = 1; valueIndex < dataLength; valueIndex++)
			{
				byte value = data[valueIndex];
				int insertionIndex;
				for (insertionIndex = valueIndex - 1; insertionIndex >= 0 && data[insertionIndex] > value; insertionIndex--)
				{
					data[insertionIndex + 1] = data[insertionIndex];
				}
				data[insertionIndex + 1] = value;
			}
		}
		public static bool pointInPoly(int numVerts, float* verts, float* point)
		{
			bool inPoly = false;
			for (int i = 0, j = numVerts - 1; i < numVerts; j = i++)
			{
				float* vi = &verts[i * 3];
				float* vj = &verts[j * 3];
				if ((vi[2] > point[2]) == (vj[2] > point[2]))
				{
					continue;
				}
				if (point[0] >= (vj[0] - vi[0]) * (point[2] - vi[2]) / (vj[2] - vi[2]) + vi[0])
				{
					continue;
				}
				inPoly = !inPoly;
			}
			return inPoly;
		}
		public static void rcVsafeNormalize(float* v)
		{
			float sqMag = rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2]);
			if (sqMag > EPSILON)
			{
				float inverseMag = 1.0f / rcSqrt(sqMag);
				v[0] *= inverseMag;
				v[1] *= inverseMag;
				v[2] *= inverseMag;
			}
		}
		public static int getCornerHeight(int x, int y, int i, int dir, ref rcCompactHeightfield chf, ref bool isBorderVertex)
		{
			ref rcCompactSpan s = ref chf.spans[i];
			int ch = (int)s.y;
			int dirp = (dir + 1) & 0x3;
			uint* regs = stackalloc uint[4] { 0, 0, 0, 0 };
			regs[0] = (uint)(chf.spans[i].reg | (chf.areas[i] << 16));
			if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
			{
				int ax = x + rcGetDirOffsetX(dir);
				int ay = y + rcGetDirOffsetY(dir);
				int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref s, dir);
				ref rcCompactSpan as1 = ref chf.spans[ai];
				ch = rcMax(ch, (int)as1.y);
				regs[1] = (uint)(chf.spans[ai].reg | (chf.areas[ai] << 16));
				if (rcGetCon(ref as1, dirp) != RC_NOT_CONNECTED)
				{
					int ax2 = ax + rcGetDirOffsetX(dirp);
					int ay2 = ay + rcGetDirOffsetY(dirp);
					int ai2 = (int)chf.cells[ax2 + ay2 * chf.width].index + rcGetCon(ref as1, dirp);
					ref rcCompactSpan as2 = ref chf.spans[ai2];
					ch = rcMax(ch, (int)as2.y);
					regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16));
				}
			}
			if (rcGetCon(ref s, dirp) != RC_NOT_CONNECTED)
			{
				int ax = x + rcGetDirOffsetX(dirp);
				int ay = y + rcGetDirOffsetY(dirp);
				int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref s, dirp);
				ref rcCompactSpan @as = ref chf.spans[ai];
				ch = rcMax(ch, (int)@as.y);
				regs[3] = (uint)(chf.spans[ai].reg | (chf.areas[ai] << 16));
				if (rcGetCon(ref @as, dir) != RC_NOT_CONNECTED)
				{
					int ax2 = ax + rcGetDirOffsetX(dir);
					int ay2 = ay + rcGetDirOffsetY(dir);
					int ai2 = (int)chf.cells[ax2 + ay2 * chf.width].index + rcGetCon(ref @as, dir);
					ref rcCompactSpan as2 = ref chf.spans[ai2];
					ch = rcMax(ch, (int)as2.y);
					regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16));
				}
			}
			for (int j = 0; j < 4; ++j)
			{
				int a = j;
				int b = (j + 1) & 0x3;
				int c = (j + 2) & 0x3;
				int d = (j + 3) & 0x3;
				bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b];
				bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0;
				bool intsSameArea = (regs[c] >> 16) == (regs[d] >> 16);
				bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0;
				if (twoSameExts && twoInts && intsSameArea && noZeros)
				{
					isBorderVertex = true;
					break;
				}
			}
			return ch;
		}
		public static void walkContour(int x, int y, int i, ref rcCompactHeightfield chf, byte* flags, ref rcIntArray points)
		{
			byte dir = 0;
			while ((flags[i] & (1 << dir)) == 0)
				dir++;
			byte startDir = dir;
			int starti = i;
			byte area = chf.areas[i];
			int iter = 0;
			while (++iter < 40000)
			{
				if (0 != (flags[i] & (1 << dir)))
				{
					bool isBorderVertex = false;
					bool isAreaBorder = false;
					int px = x;
					int py = getCornerHeight(x, y, i, dir, ref chf, ref isBorderVertex);
					int pz = y;
					switch (dir)
					{
						case 0: pz++; break;
						case 1: px++; pz++; break;
						case 2: px++; break;
					}
					int r = 0;
					ref rcCompactSpan s = ref chf.spans[i];
					if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
					{
						int ax = x + rcGetDirOffsetX(dir);
						int ay = y + rcGetDirOffsetY(dir);
						int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref s, dir);
						r = (int)chf.spans[ai].reg;
						if (area != chf.areas[ai])
							isAreaBorder = true;
					}
					if (isBorderVertex)
						r |= RC_BORDER_VERTEX;
					if (isAreaBorder)
						r |= RC_AREA_BORDER;
					points.push(px);
					points.push(py);
					points.push(pz);
					points.push(r);
					flags[i] = (byte)(flags[i] & ~(1 << dir));
					dir = (byte)((dir + 1) & 0x3);
				}
				else
				{
					int ni = -1;
					int nx = x + rcGetDirOffsetX(dir);
					int ny = y + rcGetDirOffsetY(dir);
					ref rcCompactSpan s = ref chf.spans[i];
					if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
					{
						ref rcCompactCell nc = ref chf.cells[nx + ny * chf.width];
						ni = (int)nc.index + rcGetCon(ref s, dir);
					}
					if (ni == -1)
					{
						return;
					}
					x = nx;
					y = ny;
					i = ni;
					dir = (byte)((dir + 3) & 0x3);
				}
				if (starti == i && startDir == dir)
				{
					break;
				}
			}
		}
		public static float distancePtSeg(int x, int z, int px, int pz, int qx, int qz)
		{
			float pqx = (float)(qx - px);
			float pqz = (float)(qz - pz);
			float dx = (float)(x - px);
			float dz = (float)(z - pz);
			float d = pqx * pqx + pqz * pqz;
			float t = pqx * dx + pqz * dz;
			if (d > 0)
				t /= d;
			if (t < 0)
				t = 0;
			else if (t > 1)
				t = 1;
			dx = px + t * pqx - x;
			dz = pz + t * pqz - z;
			return dx * dx + dz * dz;
		}
		public static void simplifyContour(ref rcIntArray points, ref rcIntArray simplified, float maxError, int maxEdgeLen, int buildFlags)
		{
			bool hasConnections = false;
			for (int i = 0; i < points.size(); i += 4)
			{
				if ((points[i + 3] & RC_CONTOUR_REG_MASK) != 0)
				{
					hasConnections = true;
					break;
				}
			}
			if (hasConnections)
			{
				for (int i = 0, ni = points.size() / 4; i < ni; ++i)
				{
					int ii = (i + 1) % ni;
					bool differentRegs = (points[i * 4 + 3] & RC_CONTOUR_REG_MASK) != (points[ii * 4 + 3] & RC_CONTOUR_REG_MASK);
					bool areaBorders = (points[i * 4 + 3] & RC_AREA_BORDER) != (points[ii * 4 + 3] & RC_AREA_BORDER);
					if (differentRegs || areaBorders)
					{
						simplified.push(points[i * 4 + 0]);
						simplified.push(points[i * 4 + 1]);
						simplified.push(points[i * 4 + 2]);
						simplified.push(i);
					}
				}
			}
			if (simplified.size() == 0)
			{
				int llx = points[0];
				int lly = points[1];
				int llz = points[2];
				int lli = 0;
				int urx = points[0];
				int ury = points[1];
				int urz = points[2];
				int uri = 0;
				for (int i = 0; i < points.size(); i += 4)
				{
					int x = points[i + 0];
					int y = points[i + 1];
					int z = points[i + 2];
					if (x < llx || (x == llx && z < llz))
					{
						llx = x;
						lly = y;
						llz = z;
						lli = i / 4;
					}
					if (x > urx || (x == urx && z > urz))
					{
						urx = x;
						ury = y;
						urz = z;
						uri = i / 4;
					}
				}
				simplified.push(llx);
				simplified.push(lly);
				simplified.push(llz);
				simplified.push(lli);
				simplified.push(urx);
				simplified.push(ury);
				simplified.push(urz);
				simplified.push(uri);
			}
			int pn = points.size() / 4;
			for (int i = 0; i < simplified.size() / 4;)
			{
				int ii = (i + 1) % (simplified.size() / 4);
				int ax = simplified[i * 4 + 0];
				int az = simplified[i * 4 + 2];
				int ai = simplified[i * 4 + 3];
				int bx = simplified[ii * 4 + 0];
				int bz = simplified[ii * 4 + 2];
				int bi = simplified[ii * 4 + 3];
				float maxd = 0;
				int maxi = -1;
				int ci, cinc, endi;
				if (bx > ax || (bx == ax && bz > az))
				{
					cinc = 1;
					ci = (ai + cinc) % pn;
					endi = bi;
				}
				else
				{
					cinc = pn - 1;
					ci = (bi + cinc) % pn;
					endi = ai;
					rcSwap(ref ax, ref bx);
					rcSwap(ref az, ref bz);
				}
				if ((points[ci * 4 + 3] & RC_CONTOUR_REG_MASK) == 0 || (points[ci * 4 + 3] & RC_AREA_BORDER) != 0)
				{
					while (ci != endi)
					{
						float d = distancePtSeg(points[ci * 4 + 0], points[ci * 4 + 2], ax, az, bx, bz);
						if (d > maxd)
						{
							maxd = d;
							maxi = ci;
						}
						ci = (ci + cinc) % pn;
					}
				}
				if (maxi != -1 && maxd > (maxError * maxError))
				{
					simplified.resize(simplified.size() + 4);
					int n = simplified.size() / 4;
					for (int j = n - 1; j > i; --j)
					{
						simplified[j * 4 + 0] = simplified[(j - 1) * 4 + 0];
						simplified[j * 4 + 1] = simplified[(j - 1) * 4 + 1];
						simplified[j * 4 + 2] = simplified[(j - 1) * 4 + 2];
						simplified[j * 4 + 3] = simplified[(j - 1) * 4 + 3];
					}
					simplified[(i + 1) * 4 + 0] = points[maxi * 4 + 0];
					simplified[(i + 1) * 4 + 1] = points[maxi * 4 + 1];
					simplified[(i + 1) * 4 + 2] = points[maxi * 4 + 2];
					simplified[(i + 1) * 4 + 3] = maxi;
				}
				else
				{
					++i;
				}
			}
			if (maxEdgeLen > 0 && (buildFlags & (int)(rcBuildContoursFlags.RC_CONTOUR_TESS_WALL_EDGES | rcBuildContoursFlags.RC_CONTOUR_TESS_AREA_EDGES)) != 0)
			{
				for (int i = 0; i < simplified.size() / 4;)
				{
					int ii = (i + 1) % (simplified.size() / 4);
					int ax = simplified[i * 4 + 0];
					int az = simplified[i * 4 + 2];
					int ai = simplified[i * 4 + 3];
					int bx = simplified[ii * 4 + 0];
					int bz = simplified[ii * 4 + 2];
					int bi = simplified[ii * 4 + 3];
					int maxi = -1;
					int ci = (ai + 1) % pn;
					bool tess = false;
					if ((buildFlags & (int)rcBuildContoursFlags.RC_CONTOUR_TESS_WALL_EDGES) != 0 && (points[ci * 4 + 3] & RC_CONTOUR_REG_MASK) == 0)
						tess = true;
					if ((buildFlags & (int)rcBuildContoursFlags.RC_CONTOUR_TESS_AREA_EDGES) != 0 && (points[ci * 4 + 3] & RC_AREA_BORDER) != 0)
						tess = true;
					if (tess)
					{
						int dx = bx - ax;
						int dz = bz - az;
						if (dx * dx + dz * dz > maxEdgeLen * maxEdgeLen)
						{
							int n = bi < ai ? (bi + pn - ai) : (bi - ai);
							if (n > 1)
							{
								if (bx > ax || (bx == ax && bz > az))
									maxi = (ai + n / 2) % pn;
								else
									maxi = (ai + (n + 1) / 2) % pn;
							}
						}
					}
					if (maxi != -1)
					{
						simplified.resize(simplified.size() + 4);
						int n = simplified.size() / 4;
						for (int j = n - 1; j > i; --j)
						{
							simplified[j * 4 + 0] = simplified[(j - 1) * 4 + 0];
							simplified[j * 4 + 1] = simplified[(j - 1) * 4 + 1];
							simplified[j * 4 + 2] = simplified[(j - 1) * 4 + 2];
							simplified[j * 4 + 3] = simplified[(j - 1) * 4 + 3];
						}
						simplified[(i + 1) * 4 + 0] = points[maxi * 4 + 0];
						simplified[(i + 1) * 4 + 1] = points[maxi * 4 + 1];
						simplified[(i + 1) * 4 + 2] = points[maxi * 4 + 2];
						simplified[(i + 1) * 4 + 3] = maxi;
					}
					else
					{
						++i;
					}
				}
			}
			for (int i = 0; i < simplified.size() / 4; ++i)
			{
				int ai = (simplified[i * 4 + 3] + 1) % pn;
				int bi = simplified[i * 4 + 3];
				simplified[i * 4 + 3] = (points[ai * 4 + 3] & (RC_CONTOUR_REG_MASK | RC_AREA_BORDER)) | (points[bi * 4 + 3] & RC_BORDER_VERTEX);
			}
		}
		public static int calcAreaOfPolygon2D(int* verts, int nverts)
		{
			int area = 0;
			for (int i = 0, j = nverts - 1; i < nverts; j = i++)
			{
				int* vi = &verts[i * 4];
				int* vj = &verts[j * 4];
				area += vi[0] * vj[2] - vj[0] * vi[2];
			}
			return (area + 1) / 2;
		}
		public static int prev(int i, int n)
		{ return i - 1 >= 0 ? i - 1 : n - 1; }
		public static int next(int i, int n)
		{ return i + 1 < n ? i + 1 : 0; }
		public static int area2(int* a, int* b, int* c)
		{
			return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]);
		}
		public static bool xorb(bool x, bool y)
		{
			return !x ^ !y;
		}
		public static bool left(int* a, int* b, int* c)
		{
			return area2(a, b, c) < 0;
		}
		public static bool leftOn(int* a, int* b, int* c)
		{
			return area2(a, b, c) <= 0;
		}
		public static bool collinear(int* a, int* b, int* c)
		{
			return area2(a, b, c) == 0;
		}
		public static bool intersectProp(int* a, int* b, int* c, int* d)
		{
			if (collinear(a, b, c) || collinear(a, b, d) ||
			collinear(c, d, a) || collinear(c, d, b))
				return false;
			return xorb(left(a, b, c), left(a, b, d)) && xorb(left(c, d, a), left(c, d, b));
		}
		public static bool between(int* a, int* b, int* c)
		{
			if (!collinear(a, b, c))
				return false;
			if (a[0] != b[0])
				return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0]));
			else
				return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2]));
		}
		public static bool intersect(int* a, int* b, int* c, int* d)
		{
			if (intersectProp(a, b, c, d))
				return true;
			else if (between(a, b, c) || between(a, b, d) ||
			 between(c, d, a) || between(c, d, b))
				return true;
			else
				return false;
		}
		public static bool vequal(int* a, int* b)
		{
			return a[0] == b[0] && a[2] == b[2];
		}
		public static bool intersectSegContour(int* d0, int* d1, int i, int n, int* verts)
		{
			for (int k = 0; k < n; k++)
			{
				int k1 = next(k, n);
				if (i == k || i == k1)
					continue;
				int* p0 = &verts[k * 4];
				int* p1 = &verts[k1 * 4];
				if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1))
					continue;
				if (intersect(d0, d1, p0, p1))
					return true;
			}
			return false;
		}
		public static bool inCone(int i, int n, int* verts, int* pj)
		{
			int* pi = &verts[i * 4];
			int* pi1 = &verts[next(i, n) * 4];
			int* pin1 = &verts[prev(i, n) * 4];
			if (leftOn(pin1, pi, pi1))
				return left(pi, pj, pin1) && left(pj, pi, pi1);
			return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1));
		}
		public static void removeDegenerateSegments(ref rcIntArray simplified)
		{
			int npts = simplified.size() / 4;
			for (int i = 0; i < npts; ++i)
			{
				int ni = next(i, npts);
				if (vequal(simplified.Data + (i * 4), simplified.Data + (ni * 4)))
				{
					for (int j = i; j < simplified.size() / 4 - 1; ++j)
					{
						simplified[j * 4 + 0] = simplified[(j + 1) * 4 + 0];
						simplified[j * 4 + 1] = simplified[(j + 1) * 4 + 1];
						simplified[j * 4 + 2] = simplified[(j + 1) * 4 + 2];
						simplified[j * 4 + 3] = simplified[(j + 1) * 4 + 3];
					}
					simplified.resize(simplified.size() - 4);
					npts--;
				}
			}
		}
		public static bool mergeContours(ref rcContour ca, ref rcContour cb, int ia, int ib)
		{
			int maxVerts = ca.nverts + cb.nverts + 2;
			int* verts = (int*)rcAlloc(sizeof(int) * maxVerts * 4, rcAllocHint.RC_ALLOC_PERM);
			if (null == verts)
				return false;
			int nv = 0;
			for (int i = 0; i <= ca.nverts; ++i)
			{
				int* dst = &verts[nv * 4];
				int* src = &ca.verts[((ia + i) % ca.nverts) * 4];
				dst[0] = src[0];
				dst[1] = src[1];
				dst[2] = src[2];
				dst[3] = src[3];
				nv++;
			}
			for (int i = 0; i <= cb.nverts; ++i)
			{
				int* dst = &verts[nv * 4];
				int* src = &cb.verts[((ib + i) % cb.nverts) * 4];
				dst[0] = src[0];
				dst[1] = src[1];
				dst[2] = src[2];
				dst[3] = src[3];
				nv++;
			}
			rcFree(ca.verts);
			ca.verts = verts;
			ca.nverts = nv;
			rcFree(cb.verts);
			cb.verts = (int*)0;
			cb.nverts = 0;
			return true;
		}
		public static void findLeftMostVertex(rcContour* contour, int* minx, int* minz, int* leftmost)
		{
			*minx = contour->verts[0];
			*minz = contour->verts[2];
			*leftmost = 0;
			for (int i = 1; i < contour->nverts; i++)
			{
				int x = contour->verts[i * 4 + 0];
				int z = contour->verts[i * 4 + 2];
				if (x < *minx || (x == *minx && z < *minz))
				{
					*minx = x;
					*minz = z;
					*leftmost = i;
				}
			}
		}
		public static int compareHoles(void* va, void* vb)
		{
			rcContourHole* a = (rcContourHole*)va;
			rcContourHole* b = (rcContourHole*)vb;
			if (a->minx == b->minx)
			{
				if (a->minz < b->minz)
					return -1;
				if (a->minz > b->minz)
					return 1;
			}
			else
			{
				if (a->minx < b->minx)
					return -1;
				if (a->minx > b->minx)
					return 1;
			}
			return 0;
		}
		public static int compareDiagDist(void* va, void* vb)
		{
			rcPotentialDiagonal* a = (rcPotentialDiagonal*)va;
			rcPotentialDiagonal* b = (rcPotentialDiagonal*)vb;
			if (a->dist < b->dist)
				return -1;
			if (a->dist > b->dist)
				return 1;
			return 0;
		}
		public static void mergeRegionHoles(rcContext* ctx, ref rcContourRegion region)
		{
			for (int i = 0; i < region.nholes; i++)
				findLeftMostVertex(region.holes[i].contour, &region.holes[i].minx, &region.holes[i].minz, &region.holes[i].leftmost);
			qsort(region.holes, region.nholes, sizeof(rcContourHole), compareHoles);
			int maxVerts = region.Outline->nverts;
			for (int i = 0; i < region.nholes; i++)
				maxVerts += region.holes[i].contour->nverts;
			rcScopedDelete<rcPotentialDiagonal> diags = new rcScopedDelete<rcPotentialDiagonal>((rcPotentialDiagonal*)rcAlloc(sizeof(rcPotentialDiagonal) * maxVerts, rcAllocHint.RC_ALLOC_TEMP));
			if (null == diags)
			{
				//ctx->log(RC_LOG_WARNING, "mergeRegionHoles: Failed to allocated diags %d.", maxVerts);
				return;
			}
			rcContour* Outline = region.Outline;
			for (int i = 0; i < region.nholes; i++)
			{
				rcContour* hole = region.holes[i].contour;
				int index = -1;
				int bestVertex = region.holes[i].leftmost;
				for (int iter = 0; iter < hole->nverts; iter++)
				{
					int ndiags = 0;
					int* corner = &hole->verts[bestVertex * 4];
					for (int j = 0; j < Outline->nverts; j++)
					{
						if (inCone(j, Outline->nverts, Outline->verts, corner))
						{
							int dx = Outline->verts[j * 4 + 0] - corner[0];
							int dz = Outline->verts[j * 4 + 2] - corner[2];
							diags[ndiags].vert = j;
							diags[ndiags].dist = dx * dx + dz * dz;
							ndiags++;
						}
					}
					qsort(diags, ndiags, sizeof(rcPotentialDiagonal), compareDiagDist);
					index = -1;
					for (int j = 0; j < ndiags; j++)
					{
						int* pt = &Outline->verts[diags[j].vert * 4];
						bool intersect = intersectSegContour(pt, corner, diags[i].vert, Outline->nverts, Outline->verts);
						for (int k = i; k < region.nholes && !intersect; k++)
							intersect |= intersectSegContour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts);
						if (!intersect)
						{
							index = diags[j].vert;
							break;
						}
					}
					if (index != -1)
						break;
					bestVertex = (bestVertex + 1) % hole->nverts;
				}
				if (index == -1)
				{
					//ctx->log(RC_LOG_WARNING, "mergeHoles: Failed to find merge points for %p and %p.", region.Outline, hole);
					continue;
				}
				if (!mergeContours(ref *region.Outline, ref *hole, index, bestVertex))
				{
					//ctx->log(RC_LOG_WARNING, "mergeHoles: Failed to merge contours %p and %p.", region.Outline, hole);
					continue;
				}
			}
		}
		public static bool rcBuildContours(rcContext* ctx, ref rcCompactHeightfield chf, float maxError, int maxEdgeLen, ref rcContourSet cset, int buildFlags = (int)rcBuildContoursFlags.RC_CONTOUR_TESS_WALL_EDGES)
		{
			rcAssert(ctx);
			int w = chf.width;
			int h = chf.height;
			int borderSize = chf.borderSize;
			//rcScopedTimer timer(ctx, RC_TIMER_BUILD_CONTOURS);
			rcContourSet* cset_ptr = (rcContourSet*)Unsafe.AsPointer(ref cset);
			rcCompactHeightfield* chf_ptr = (rcCompactHeightfield*)Unsafe.AsPointer(ref chf);
			rcVcopy(cset_ptr->bmin, chf_ptr->bmin);
			rcVcopy(cset_ptr->bmax, chf_ptr->bmax);
			if (borderSize > 0)
			{
				float pad = borderSize * chf.cs;
				cset.bmin[0] += pad;
				cset.bmin[2] += pad;
				cset.bmax[0] -= pad;
				cset.bmax[2] -= pad;
			}
			cset.cs = chf.cs;
			cset.ch = chf.ch;
			cset.width = chf.width - chf.borderSize * 2;
			cset.height = chf.height - chf.borderSize * 2;
			cset.borderSize = chf.borderSize;
			cset.maxError = maxError;
			int maxContours = rcMax((int)chf.maxRegions, 8);
			cset.conts = (rcContour*)rcAlloc(sizeof(rcContour) * maxContours, rcAllocHint.RC_ALLOC_PERM);
			if (null == cset.conts)
				return false;
			cset.nconts = 0;
			rcScopedDelete<byte> flags = new rcScopedDelete<byte>((byte*)rcAlloc(sizeof(byte) * chf.spanCount, rcAllocHint.RC_ALLOC_TEMP));
			if (null == flags)
			{
				//ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount);
				return false;
			}
			////ctx->startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						byte res = 0;
						ref rcCompactSpan s = ref chf.spans[i];
						if (0 == chf.spans[i].reg || (chf.spans[i].reg & RC_BORDER_REG) != 0)
						{
							flags[i] = 0;
							continue;
						}
						for (int dir = 0; dir < 4; ++dir)
						{
							ushort r = 0;
							if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
							{
								int ax = x + rcGetDirOffsetX(dir);
								int ay = y + rcGetDirOffsetY(dir);
								int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
								r = chf.spans[ai].reg;
							}
							if (r == chf.spans[i].reg)
								res = (byte)(res | (1 << dir));
						}
						flags[i] = (byte)(res ^ 0xf);
					}
				}
			}
			////ctx->stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);
			rcIntArray verts = new rcIntArray(256);
			rcIntArray simplified = new rcIntArray(64);
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						if (flags[i] == 0 || flags[i] == 0xf)
						{
							flags[i] = 0;
							continue;
						}
						ushort reg = chf.spans[i].reg;
						if (0 == reg || (reg & RC_BORDER_REG) != 0)
							continue;
						byte area = chf.areas[i];
						verts.clear();
						simplified.clear();
						//ctx->startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);
						walkContour(x, y, i, ref chf, flags, ref verts);
						//ctx->stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);
						//ctx->startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY);
						simplifyContour(ref verts, ref simplified, maxError, maxEdgeLen, buildFlags);
						removeDegenerateSegments(ref simplified);
						//ctx->stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY);
						if (simplified.size() / 4 >= 3)
						{
							if (cset.nconts >= maxContours)
							{
								int oldMax = maxContours;
								maxContours *= 2;
								rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour) * maxContours, rcAllocHint.RC_ALLOC_PERM);
								for (int j = 0; j < cset.nconts; ++j)
								{
									newConts[j] = cset.conts[j];
									cset.conts[j].verts = (int*)0;
									cset.conts[j].rverts = (int*)0;
								}
								rcFree(cset.conts);
								cset.conts = newConts;
								//ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours);
							}
							rcContour* cont = &cset.conts[cset.nconts++];
							cont->nverts = simplified.size() / 4;
							cont->verts = (int*)rcAlloc(sizeof(int) * cont->nverts * 4, rcAllocHint.RC_ALLOC_PERM);
							if (null == cont->verts)
							{
								//ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts);
								return false;
							}
							memcpy(cont->verts, &simplified.m_impl.m_data[0], sizeof(int) * cont->nverts * 4);
							if (borderSize > 0)
							{
								for (int j = 0; j < cont->nverts; ++j)
								{
									int* v = &cont->verts[j * 4];
									v[0] -= borderSize;
									v[2] -= borderSize;
								}
							}
							cont->nrverts = verts.size() / 4;
							cont->rverts = (int*)rcAlloc(sizeof(int) * cont->nrverts * 4, rcAllocHint.RC_ALLOC_PERM);
							if (null == cont->rverts)
							{
								//ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts);
								return false;
							}
							memcpy(cont->rverts, &verts.m_impl.m_data[0], sizeof(int) * cont->nrverts * 4);
							if (borderSize > 0)
							{
								for (int j = 0; j < cont->nrverts; ++j)
								{
									int* v = &cont->rverts[j * 4];
									v[0] -= borderSize;
									v[2] -= borderSize;
								}
							}
							cont->reg = reg;
							cont->area = area;
						}
					}
				}
			}
			if (cset.nconts > 0)
			{
				rcScopedDelete<sbyte> winding = new rcScopedDelete<sbyte>((sbyte*)rcAlloc(sizeof(sbyte) * cset.nconts, rcAllocHint.RC_ALLOC_TEMP));
				if (null == winding)
				{
					//ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts);
					return false;
				}
				int nholes = 0;
				for (int i = 0; i < cset.nconts; ++i)
				{
					ref rcContour cont = ref cset.conts[i];
					winding[i] = (sbyte)(calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0 ? -1 : 1);
					if (winding[i] < 0)
						nholes++;
				}
				if (nholes > 0)
				{
					int nregions = chf.maxRegions + 1;
					rcScopedDelete<rcContourRegion> regions = new rcScopedDelete<rcContourRegion>((rcContourRegion*)rcAlloc(sizeof(rcContourRegion) * nregions, rcAllocHint.RC_ALLOC_TEMP));
					if (null == regions)
					{
						//ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'regions' (%d).", nregions);
						return false;
					}
					memset(regions, 0, sizeof(rcContourRegion) * nregions);
					rcScopedDelete<rcContourHole> holes = new rcScopedDelete<rcContourHole>((rcContourHole*)rcAlloc(sizeof(rcContourHole) * cset.nconts, rcAllocHint.RC_ALLOC_TEMP));
					if (null == holes)
					{
						//ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'holes' (%d).", cset.nconts);
						return false;
					}
					memset(holes, 0, sizeof(rcContourHole) * cset.nconts);
					for (int i = 0; i < cset.nconts; ++i)
					{
						ref rcContour cont = ref cset.conts[i];
						if (winding[i] > 0)
						{
							if (regions[cont.reg].Outline != null)
								//ctx->log(RC_LOG_ERROR, "rcBuildContours: Multiple Outlines for region %d.", cont.reg);
								*regions[cont.reg].Outline = cont;
						}
						else
						{
							regions[cont.reg].nholes++;
						}
					}
					int index = 0;
					for (int i = 0; i < nregions; i++)
					{
						if (regions[i].nholes > 0)
						{
							regions[i].holes = &holes.Data[index];
							index += regions[i].nholes;
							regions[i].nholes = 0;
						}
					}
					for (int i = 0; i < cset.nconts; ++i)
					{
						rcContour* cont = &cset.conts[i];
						ref rcContourRegion reg = ref regions[cont->reg];
						if (winding[i] < 0)
							reg.holes[reg.nholes++].contour = cont;
					}
					for (int i = 0; i < nregions; i++)
					{
						ref rcContourRegion reg = ref regions[i];
						if (0 == reg.nholes) continue;
						if (reg.Outline != null)
						{
							mergeRegionHoles(ctx, ref reg);
						}
						else
						{
							//ctx->log(RC_LOG_ERROR, "rcBuildContours: Bad Outline for region %d, contour simplification is likely too aggressive.", i);
						}
					}
				}
			}
			return true;
		}
		public static bool contains(byte* a, byte an, byte v)
		{
			int n = (int)an;
			for (int i = 0; i < n; ++i)
			{
				if (a[i] == v)
					return true;
			}
			return false;
		}
		public static bool addUnique(byte* a, ref byte an, int anMax, byte v)
		{
			if (contains(a, an, v))
				return true;
			if ((int)an >= anMax)
				return false;
			a[an] = v;
			an++;
			return true;
		}
		public static bool overlapRange(ushort amin, ushort amax, ushort bmin, ushort bmax)
		{
			return (amin > bmax || amax < bmin) ? false : true;
		}
		public static bool buildMeshAdjacency(ushort* polys, int npolys, int nverts, int vertsPerPoly)
		{
			int maxEdgeCount = npolys * vertsPerPoly;
			ushort* firstEdge = (ushort*)rcAlloc(sizeof(ushort) * (nverts + maxEdgeCount), rcAllocHint.RC_ALLOC_TEMP);
			if (null == firstEdge)
				return false;
			ushort* nextEdge = firstEdge + nverts;
			int edgeCount = 0;
			rcEdge* edges = rcNewArray<rcEdge>(maxEdgeCount, rcAllocHint.RC_ALLOC_TEMP);
			if (null == edges)
			{
				rcFree(firstEdge);
				return false;
			}
			for (int i = 0; i < nverts; i++)
				firstEdge[i] = RC_MESH_NULL_IDX;
			for (int i = 0; i < npolys; ++i)
			{
				ushort* t = &polys[i * vertsPerPoly * 2];
				for (int j = 0; j < vertsPerPoly; ++j)
				{
					if (t[j] == RC_MESH_NULL_IDX) break;
					ushort v0 = t[j];
					ushort v1 = (j + 1 >= vertsPerPoly || t[j + 1] == RC_MESH_NULL_IDX) ? t[0] : t[j + 1];
					if (v0 < v1)
					{
						ref rcEdge edge = ref edges[edgeCount];
						edge.vert[0] = v0;
						edge.vert[1] = v1;
						edge.poly[0] = (ushort)i;
						edge.polyEdge[0] = (ushort)j;
						edge.poly[1] = (ushort)i;
						edge.polyEdge[1] = 0;
						nextEdge[edgeCount] = firstEdge[v0];
						firstEdge[v0] = (ushort)edgeCount;
						edgeCount++;
					}
				}
			}
			for (int i = 0; i < npolys; ++i)
			{
				ushort* t = &polys[i * vertsPerPoly * 2];
				for (int j = 0; j < vertsPerPoly; ++j)
				{
					if (t[j] == RC_MESH_NULL_IDX) break;
					ushort v0 = t[j];
					ushort v1 = (j + 1 >= vertsPerPoly || t[j + 1] == RC_MESH_NULL_IDX) ? t[0] : t[j + 1];
					if (v0 > v1)
					{
						for (ushort e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = nextEdge[e])
						{
							ref rcEdge edge = ref edges[e];
							if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1])
							{
								edge.poly[1] = (ushort)i;
								edge.polyEdge[1] = (ushort)j;
								break;
							}
						}
					}
				}
			}
			for (int i = 0; i < edgeCount; ++i)
			{
				ref rcEdge e = ref edges[i];
				if (e.poly[0] != e.poly[1])
				{
					ushort* p0 = &polys[e.poly[0] * vertsPerPoly * 2];
					ushort* p1 = &polys[e.poly[1] * vertsPerPoly * 2];
					p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1];
					p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0];
				}
			}
			rcFree(firstEdge);
			rcFree(edges);
			return true;
		}
		public static int computeVertexHash(int x, int y, int z)
		{
			uint h1 = 0x8da6b343;
			uint h2 = 0xd8163841;
			uint h3 = 0xcb1ab31f;
			uint n = (uint)(h1 * x + h2 * y + h3 * z);
			return (int)(n & (VERTEX_BUCKET_COUNT - 1));
		}
		public static ushort addVertex(ushort x, ushort y, ushort z, ushort* verts, int* firstVert, int* nextVert, ref int nv)
		{
			int bucket = computeVertexHash(x, 0, z);
			int i = firstVert[bucket];
			while (i != -1)
			{
				ushort* v1 = &verts[i * 3];
				if (v1[0] == x && (rcAbs(v1[1] - y) <= 2) && v1[2] == z)
					return (ushort)i;
				i = nextVert[i];
			}
			i = nv; nv++;
			ushort* v = &verts[i * 3];
			v[0] = x;
			v[1] = y;
			v[2] = z;
			nextVert[i] = firstVert[bucket];
			firstVert[bucket] = i;
			return (ushort)i;
		}
		public static bool diagonalie(int i, int j, int n, int* verts, int* indices)
		{
			int* d0 = &verts[(indices[i] & 0x0fffffff) * 4];
			int* d1 = &verts[(indices[j] & 0x0fffffff) * 4];
			for (int k = 0; k < n; k++)
			{
				int k1 = next(k, n);
				if (!((k == i) || (k1 == i) || (k == j) || (k1 == j)))
				{
					int* p0 = &verts[(indices[k] & 0x0fffffff) * 4];
					int* p1 = &verts[(indices[k1] & 0x0fffffff) * 4];
					if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1))
						continue;
					if (intersect(d0, d1, p0, p1))
						return false;
				}
			}
			return true;
		}
		public static bool inCone(int i, int j, int n, int* verts, int* indices)
		{
			int* pi = &verts[(indices[i] & 0x0fffffff) * 4];
			int* pj = &verts[(indices[j] & 0x0fffffff) * 4];
			int* pi1 = &verts[(indices[next(i, n)] & 0x0fffffff) * 4];
			int* pin1 = &verts[(indices[prev(i, n)] & 0x0fffffff) * 4];
			if (leftOn(pin1, pi, pi1))
				return left(pi, pj, pin1) && left(pj, pi, pi1);
			return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1));
		}
		public static bool diagonal(int i, int j, int n, int* verts, int* indices)
		{
			return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices);
		}
		public static bool diagonalieLoose(int i, int j, int n, int* verts, int* indices)
		{
			int* d0 = &verts[(indices[i] & 0x0fffffff) * 4];
			int* d1 = &verts[(indices[j] & 0x0fffffff) * 4];
			for (int k = 0; k < n; k++)
			{
				int k1 = next(k, n);
				if (!((k == i) || (k1 == i) || (k == j) || (k1 == j)))
				{
					int* p0 = &verts[(indices[k] & 0x0fffffff) * 4];
					int* p1 = &verts[(indices[k1] & 0x0fffffff) * 4];
					if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1))
						continue;
					if (intersectProp(d0, d1, p0, p1))
						return false;
				}
			}
			return true;
		}
		public static bool inConeLoose(int i, int j, int n, int* verts, int* indices)
		{
			int* pi = &verts[(indices[i] & 0x0fffffff) * 4];
			int* pj = &verts[(indices[j] & 0x0fffffff) * 4];
			int* pi1 = &verts[(indices[next(i, n)] & 0x0fffffff) * 4];
			int* pin1 = &verts[(indices[prev(i, n)] & 0x0fffffff) * 4];
			if (leftOn(pin1, pi, pi1))
				return leftOn(pi, pj, pin1) && leftOn(pj, pi, pi1);
			return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1));
		}
		public static bool diagonalLoose(int i, int j, int n, int* verts, int* indices)
		{
			return inConeLoose(i, j, n, verts, indices) && diagonalieLoose(i, j, n, verts, indices);
		}
		public static int triangulate(int n, int* verts, int* indices, int* tris)
		{
			int ntris = 0;
			int* dst = tris;
			for (int i = 0; i < n; i++)
			{
				int i1 = next(i, n);
				int i2 = next(i1, n);
				if (diagonal(i, i2, n, verts, indices))
					indices[i1] = (int)(indices[i1] | 0x80000000);
			}
			while (n > 3)
			{
				int minLen = -1;
				int mini = -1;
				for (int ix = 0; ix < n; ix++)
				{
					int i1x = next(ix, n);
					if (0 != (indices[i1x] & 0x80000000))
					{
						int* p0 = &verts[(indices[ix] & 0x0fffffff) * 4];
						int* p2 = &verts[(indices[next(i1x, n)] & 0x0fffffff) * 4];
						int dx = p2[0] - p0[0];
						int dy = p2[2] - p0[2];
						int len = dx * dx + dy * dy;
						if (minLen < 0 || len < minLen)
						{
							minLen = len;
							mini = ix;
						}
					}
				}
				if (mini == -1)
				{
					minLen = -1;
					mini = -1;
					for (int ix = 0; ix < n; ix++)
					{
						int i1x = next(ix, n);
						int i2x = next(i1x, n);
						if (diagonalLoose(ix, i2x, n, verts, indices))
						{
							int* p0 = &verts[(indices[ix] & 0x0fffffff) * 4];
							int* p2 = &verts[(indices[next(i2x, n)] & 0x0fffffff) * 4];
							int dx = p2[0] - p0[0];
							int dy = p2[2] - p0[2];
							int len = dx * dx + dy * dy;
							if (minLen < 0 || len < minLen)
							{
								minLen = len;
								mini = ix;
							}
						}
					}
					if (mini == -1)
					{
						return -ntris;
					}
				}
				int i = mini;
				int i1 = next(i, n);
				int i2 = next(i1, n);
				*dst++ = indices[i] & 0x0fffffff;
				*dst++ = indices[i1] & 0x0fffffff;
				*dst++ = indices[i2] & 0x0fffffff;
				ntris++;
				n--;
				for (int k = i1; k < n; k++)
					indices[k] = indices[k + 1];
				if (i1 >= n) i1 = 0;
				i = prev(i1, n);
				if (diagonal(prev(i, n), i1, n, verts, indices))
					indices[i] = (int)(indices[i] | 0x80000000);
				else
					indices[i] &= 0x0fffffff;
				if (diagonal(i, next(i1, n), n, verts, indices))
					indices[i1] = (int)(indices[i1] | 0x80000000);
				else
					indices[i1] &= 0x0fffffff;
			}
			*dst++ = indices[0] & 0x0fffffff;
			*dst++ = indices[1] & 0x0fffffff;
			*dst++ = indices[2] & 0x0fffffff;
			ntris++;
			return ntris;
		}
		public static int countPolyVerts(ushort* p, int nvp)
		{
			for (int i = 0; i < nvp; ++i)
				if (p[i] == RC_MESH_NULL_IDX)
					return i;
			return nvp;
		}
		public static bool uleft(ushort* a, ushort* b, ushort* c)
		{
			return (b[0] - a[0]) * (c[2] - a[2]) -
			 (c[0] - a[0]) * (b[2] - a[2]) < 0;
		}
		public static int getPolyMergeValue(ushort* pa, ushort* pb, ushort* verts, ref int ea, ref int eb, int nvp)
		{
			int na = countPolyVerts(pa, nvp);
			int nb = countPolyVerts(pb, nvp);
			if (na + nb - 2 > nvp)
				return -1;
			ea = -1;
			eb = -1;
			for (int i = 0; i < na; ++i)
			{
				ushort va0 = pa[i];
				ushort va1 = pa[(i + 1) % na];
				if (va0 > va1)
					rcSwap(ref va0, ref va1);
				for (int j = 0; j < nb; ++j)
				{
					ushort vb0 = pb[j];
					ushort vb1 = pb[(j + 1) % nb];
					if (vb0 > vb1)
						rcSwap(ref vb0, ref vb1);
					if (va0 == vb0 && va1 == vb1)
					{
						ea = i;
						eb = j;
						break;
					}
				}
			}
			if (ea == -1 || eb == -1)
				return -1;
			ushort va, vb, vc;
			va = pa[(ea + na - 1) % na];
			vb = pa[ea];
			vc = pb[(eb + 2) % nb];
			if (!uleft(&verts[va * 3], &verts[vb * 3], &verts[vc * 3]))
				return -1;
			va = pb[(eb + nb - 1) % nb];
			vb = pb[eb];
			vc = pa[(ea + 2) % na];
			if (!uleft(&verts[va * 3], &verts[vb * 3], &verts[vc * 3]))
				return -1;
			va = pa[ea];
			vb = pa[(ea + 1) % na];
			int dx = verts[va * 3 + 0] - verts[vb * 3 + 0];
			int dy = verts[va * 3 + 2] - verts[vb * 3 + 2];
			return dx * dx + dy * dy;
		}
		public static void mergePolyVerts(ushort* pa, ushort* pb, int ea, int eb, ushort* tmp, int nvp)
		{
			int na = countPolyVerts(pa, nvp);
			int nb = countPolyVerts(pb, nvp);
			memset(tmp, 0xff, sizeof(ushort) * nvp);
			int n = 0;
			for (int i = 0; i < na - 1; ++i)
				tmp[n++] = pa[(ea + 1 + i) % na];
			for (int i = 0; i < nb - 1; ++i)
				tmp[n++] = pb[(eb + 1 + i) % nb];
			memcpy(pa, tmp, sizeof(ushort) * nvp);
		}
		public static void pushFront(int v, int* arr, ref int an)
		{
			an++;
			for (int i = an - 1; i > 0; --i) arr[i] = arr[i - 1];
			arr[0] = v;
		}
		public static void pushBack(int v, int* arr, ref int an)
		{
			arr[an] = v;
			an++;
		}
		public static bool canRemoveVertex(rcContext* ctx, ref rcPolyMesh mesh, ushort rem)
		{
			int nvp = mesh.nvp;
			int numTouchedVerts = 0;
			int numRemainingEdges = 0;
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				int nv = countPolyVerts(p, nvp);
				int numRemoved = 0;
				int numVerts = 0;
				for (int j = 0; j < nv; ++j)
				{
					if (p[j] == rem)
					{
						numTouchedVerts++;
						numRemoved++;
					}
					numVerts++;
				}
				if (0 != numRemoved)
				{
					numRemainingEdges += numVerts - (numRemoved + 1);
				}
			}
			if (numRemainingEdges <= 2)
				return false;
			int maxEdges = numTouchedVerts * 2;
			int nedges = 0;
			rcScopedDelete<int> edges = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * maxEdges * 3, rcAllocHint.RC_ALLOC_TEMP));
			if (null == edges)
			{
				//ctx->log(RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' (%d).", maxEdges * 3);
				return false;
			}
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				int nv = countPolyVerts(p, nvp);
				for (int j = 0, k = nv - 1; j < nv; k = j++)
				{
					if (p[j] == rem || p[k] == rem)
					{
						int a = p[j], b = p[k];
						if (b == rem)
							rcSwap(ref a, ref b);
						bool exists = false;
						for (int m = 0; m < nedges; ++m)
						{
							int* e = &edges.Data[m * 3];
							if (e[1] == b)
							{
								e[2]++;
								exists = true;
							}
						}
						if (!exists)
						{
							int* e = &edges.Data[nedges * 3];
							e[0] = a;
							e[1] = b;
							e[2] = 1;
							nedges++;
						}
					}
				}
			}
			int numOpenEdges = 0;
			for (int i = 0; i < nedges; ++i)
			{
				if (edges[i * 3 + 2] < 2)
					numOpenEdges++;
			}
			if (numOpenEdges > 2)
				return false;
			return true;
		}
		public static bool removeVertex(rcContext* ctx, ref rcPolyMesh mesh, ushort rem, int maxTris)
		{
			int nvp = mesh.nvp;
			int numRemovedVerts = 0;
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				int nv = countPolyVerts(p, nvp);
				for (int j = 0; j < nv; ++j)
				{
					if (p[j] == rem)
						numRemovedVerts++;
				}
			}
			int nedges = 0;
			rcScopedDelete<int> edges = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * numRemovedVerts * nvp * 4, rcAllocHint.RC_ALLOC_TEMP));
			if (null == edges)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", numRemovedVerts * nvp * 4);
				return false;
			}
			int nhole = 0;
			rcScopedDelete<int> hole = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * numRemovedVerts * nvp, rcAllocHint.RC_ALLOC_TEMP));
			if (null == hole)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", numRemovedVerts * nvp);
				return false;
			}
			int nhreg = 0;
			rcScopedDelete<int> hreg = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * numRemovedVerts * nvp, rcAllocHint.RC_ALLOC_TEMP));
			if (null == hreg)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", numRemovedVerts * nvp);
				return false;
			}
			int nharea = 0;
			rcScopedDelete<int> harea = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * numRemovedVerts * nvp, rcAllocHint.RC_ALLOC_TEMP));
			if (null == harea)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'harea' (%d).", numRemovedVerts * nvp);
				return false;
			}
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				int nv = countPolyVerts(p, nvp);
				bool hasRem = false;
				for (int j = 0; j < nv; ++j)
					if (p[j] == rem) hasRem = true;
				if (hasRem)
				{
					for (int j = 0, k = nv - 1; j < nv; k = j++)
					{
						if (p[j] != rem && p[k] != rem)
						{
							int* e = &edges.Data[nedges * 4];
							e[0] = p[k];
							e[1] = p[j];
							e[2] = mesh.regs[i];
							e[3] = mesh.areas[i];
							nedges++;
						}
					}
					ushort* p2 = &mesh.polys[(mesh.npolys - 1) * nvp * 2];
					if (p != p2)
						memcpy(p, p2, sizeof(ushort) * nvp);
					memset(p + nvp, 0xff, sizeof(ushort) * nvp);
					mesh.regs[i] = mesh.regs[mesh.npolys - 1];
					mesh.areas[i] = mesh.areas[mesh.npolys - 1];
					mesh.npolys--;
					--i;
				}
			}
			for (int i = (int)rem; i < mesh.nverts - 1; ++i)
			{
				mesh.verts[i * 3 + 0] = mesh.verts[(i + 1) * 3 + 0];
				mesh.verts[i * 3 + 1] = mesh.verts[(i + 1) * 3 + 1];
				mesh.verts[i * 3 + 2] = mesh.verts[(i + 1) * 3 + 2];
			}
			mesh.nverts--;
			for (int i = 0; i < mesh.npolys; ++i)
			{
				ushort* p = &mesh.polys[i * nvp * 2];
				int nv = countPolyVerts(p, nvp);
				for (int j = 0; j < nv; ++j)
					if (p[j] > rem) p[j]--;
			}
			for (int i = 0; i < nedges; ++i)
			{
				if (edges[i * 4 + 0] > rem) edges[i * 4 + 0]--;
				if (edges[i * 4 + 1] > rem) edges[i * 4 + 1]--;
			}
			if (nedges == 0)
				return true;
			pushBack(edges[0], hole, ref nhole);
			pushBack(edges[2], hreg, ref nhreg);
			pushBack(edges[3], harea, ref nharea);
			while (0 != nedges)
			{
				bool match = false;
				for (int i = 0; i < nedges; ++i)
				{
					int ea = edges[i * 4 + 0];
					int eb = edges[i * 4 + 1];
					int r = edges[i * 4 + 2];
					int a = edges[i * 4 + 3];
					bool add = false;
					if (hole[0] == eb)
					{
						pushFront(ea, hole, ref nhole);
						pushFront(r, hreg, ref nhreg);
						pushFront(a, harea, ref nharea);
						add = true;
					}
					else if (hole[nhole - 1] == ea)
					{
						pushBack(eb, hole, ref nhole);
						pushBack(r, hreg, ref nhreg);
						pushBack(a, harea, ref nharea);
						add = true;
					}
					if (add)
					{
						edges[i * 4 + 0] = edges[(nedges - 1) * 4 + 0];
						edges[i * 4 + 1] = edges[(nedges - 1) * 4 + 1];
						edges[i * 4 + 2] = edges[(nedges - 1) * 4 + 2];
						edges[i * 4 + 3] = edges[(nedges - 1) * 4 + 3];
						--nedges;
						match = true;
						--i;
					}
				}
				if (!match)
					break;
			}
			rcScopedDelete<int> tris = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * nhole * 3, rcAllocHint.RC_ALLOC_TEMP));
			if (null == tris)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole * 3);
				return false;
			}
			rcScopedDelete<int> tverts = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * nhole * 4, rcAllocHint.RC_ALLOC_TEMP));
			if (null == tverts)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole * 4);
				return false;
			}
			rcScopedDelete<int> thole = new rcScopedDelete<int>((int*)rcAlloc(sizeof(int) * nhole, rcAllocHint.RC_ALLOC_TEMP));
			if (null == thole)
			{
				//ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole);
				return false;
			}
			for (int i = 0; i < nhole; ++i)
			{
				int pi = hole[i];
				tverts[i * 4 + 0] = mesh.verts[pi * 3 + 0];
				tverts[i * 4 + 1] = mesh.verts[pi * 3 + 1];
				tverts[i * 4 + 2] = mesh.verts[pi * 3 + 2];
				tverts[i * 4 + 3] = 0;
				thole[i] = i;
			}
			int ntris = triangulate(nhole, &tverts.Data[0], &thole.Data[0], tris);
			if (ntris < 0)
			{
				ntris = -ntris;
				//ctx->log(RC_LOG_WARNING, "removeVertex: triangulate()returned bad results.");
			}
			rcScopedDelete<ushort> polys = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * (ntris + 1) * nvp, rcAllocHint.RC_ALLOC_TEMP));
			if (null == polys)
			{
				//ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'polys' (%d).", (ntris + 1) * nvp);
				return false;
			}
			rcScopedDelete<ushort> pregs = new rcScopedDelete<dtNodeIndex>((ushort*)rcAlloc(sizeof(ushort) * ntris, rcAllocHint.RC_ALLOC_TEMP));
			if (null == pregs)
			{
				//ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' (%d).", ntris);
				return false;
			}
			rcScopedDelete<byte> pareas = new rcScopedDelete<byte>((byte*)rcAlloc(sizeof(byte) * ntris, rcAllocHint.RC_ALLOC_TEMP));
			if (null == pareas)
			{
				//ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris);
				return false;
			}
			ushort* tmpPoly = &polys.Data[ntris * nvp];
			int npolys = 0;
			memset(polys, 0xff, ntris * nvp * sizeof(ushort));
			for (int j = 0; j < ntris; ++j)
			{
				int* t = &tris.Data[j * 3];
				if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
				{
					polys[npolys * nvp + 0] = (ushort)hole[t[0]];
					polys[npolys * nvp + 1] = (ushort)hole[t[1]];
					polys[npolys * nvp + 2] = (ushort)hole[t[2]];
					if (hreg[t[0]] != hreg[t[1]] || hreg[t[1]] != hreg[t[2]])
						pregs[npolys] = RC_MULTIPLE_REGS;
					else
						pregs[npolys] = (ushort)hreg[t[0]];
					pareas[npolys] = (byte)harea[t[0]];
					npolys++;
				}
			}
			if (0 == npolys)
				return true;
			if (nvp > 3)
			{
				for (; ; )
				{
					int bestMergeVal = 0;
					int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
					for (int j = 0; j < npolys - 1; ++j)
					{
						ushort* pj = &polys.Data[j * nvp];
						for (int k = j + 1; k < npolys; ++k)
						{
							ushort* pk = &polys.Data[k * nvp];
							int ea = 0, eb = 0;
							int v = getPolyMergeValue(pj, pk, mesh.verts, ref ea, ref eb, nvp);
							if (v > bestMergeVal)
							{
								bestMergeVal = v;
								bestPa = j;
								bestPb = k;
								bestEa = ea;
								bestEb = eb;
							}
						}
					}
					if (bestMergeVal > 0)
					{
						ushort* pa = &polys.Data[bestPa * nvp];
						ushort* pb = &polys.Data[bestPb * nvp];
						mergePolyVerts(pa, pb, bestEa, bestEb, tmpPoly, nvp);
						if (pregs[bestPa] != pregs[bestPb])
							pregs[bestPa] = RC_MULTIPLE_REGS;
						ushort* last = &polys.Data[(npolys - 1) * nvp];
						if (pb != last)
							memcpy(pb, last, sizeof(ushort) * nvp);
						pregs[bestPb] = pregs[npolys - 1];
						pareas[bestPb] = pareas[npolys - 1];
						npolys--;
					}
					else
					{
						break;
					}
				}
			}
			for (int i = 0; i < npolys; ++i)
			{
				if (mesh.npolys >= maxTris) break;
				ushort* p = &mesh.polys[mesh.npolys * nvp * 2];
				memset(p, 0xff, sizeof(ushort) * nvp * 2);
				for (int j = 0; j < nvp; ++j)
					p[j] = polys[i * nvp + j];
				mesh.regs[mesh.npolys] = pregs[i];
				mesh.areas[mesh.npolys] = pareas[i];
				mesh.npolys++;
				if (mesh.npolys > maxTris)
				{
					//ctx->log(RC_LOG_ERROR, "removeVertex: Too many polygons %d (max:%d).", mesh.npolys, maxTris);
					return false;
				}
			}
			return true;
		}
		public static float vdot2(float* a, float* b)
		{
			return a[0] * b[0] + a[2] * b[2];
		}
		public static float vdistSq2(float* p, float* q)
		{
			float dx = q[0] - p[0];
			float dy = q[2] - p[2];
			return dx * dx + dy * dy;
		}
		public static float vdist2(float* p, float* q)
		{
			return sqrtf(vdistSq2(p, q));
		}
		public static float vcross2(float* p1, float* p2, float* p3)
		{
			float u1 = p2[0] - p1[0];
			float v1 = p2[2] - p1[2];
			float u2 = p3[0] - p1[0];
			float v2 = p3[2] - p1[2];
			return u1 * v2 - v1 * u2;
		}
		public static bool circumCircle(float* p1, float* p2, float* p3, float* c, ref float r)
		{
			float EPS = 1e-6f;
			float* v1 = stackalloc float[3] { 0, 0, 0 };
			float* v2 = stackalloc float[3], v3 = stackalloc float[3];
			rcVsub(v2, p2, p1);
			rcVsub(v3, p3, p1);
			float cp = vcross2(v1, v2, v3);
			if (fabsf(cp) > EPS)
			{
				float v1Sq = vdot2(v1, v1);
				float v2Sq = vdot2(v2, v2);
				float v3Sq = vdot2(v3, v3);
				c[0] = (v1Sq * (v2[2] - v3[2]) + v2Sq * (v3[2] - v1[2]) + v3Sq * (v1[2] - v2[2])) / (2 * cp);
				c[1] = 0;
				c[2] = (v1Sq * (v3[0] - v2[0]) + v2Sq * (v1[0] - v3[0]) + v3Sq * (v2[0] - v1[0])) / (2 * cp);
				r = vdist2(c, v1);
				rcVadd(c, c, p1);
				return true;
			}
			rcVcopy(c, p1);
			r = 0;
			return false;
		}
		public static float distPtTri(float* p, float* a, float* b, float* c)
		{
			float* v0 = stackalloc float[3], v1 = stackalloc float[3], v2 = stackalloc float[3];
			rcVsub(v0, c, a);
			rcVsub(v1, b, a);
			rcVsub(v2, p, a);
			float dot00 = vdot2(v0, v0);
			float dot01 = vdot2(v0, v1);
			float dot02 = vdot2(v0, v2);
			float dot11 = vdot2(v1, v1);
			float dot12 = vdot2(v1, v2);
			float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
			float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
			float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
			float EPS = 1e-4f;
			if (u >= -EPS && v >= -EPS && (u + v) <= 1 + EPS)
			{
				float y = a[1] + v0[1] * u + v1[1] * v;
				return fabsf(y - p[1]);
			}
			return FLT_MAX;
		}
		public static float distancePtSeg(float* pt, float* p, float* q)
		{
			float pqx = q[0] - p[0];
			float pqy = q[1] - p[1];
			float pqz = q[2] - p[2];
			float dx = pt[0] - p[0];
			float dy = pt[1] - p[1];
			float dz = pt[2] - p[2];
			float d = pqx * pqx + pqy * pqy + pqz * pqz;
			float t = pqx * dx + pqy * dy + pqz * dz;
			if (d > 0)
				t /= d;
			if (t < 0)
				t = 0;
			else if (t > 1)
				t = 1;
			dx = p[0] + t * pqx - pt[0];
			dy = p[1] + t * pqy - pt[1];
			dz = p[2] + t * pqz - pt[2];
			return dx * dx + dy * dy + dz * dz;
		}
		public static float distancePtSeg2d(float* pt, float* p, float* q)
		{
			float pqx = q[0] - p[0];
			float pqz = q[2] - p[2];
			float dx = pt[0] - p[0];
			float dz = pt[2] - p[2];
			float d = pqx * pqx + pqz * pqz;
			float t = pqx * dx + pqz * dz;
			if (d > 0)
				t /= d;
			if (t < 0)
				t = 0;
			else if (t > 1)
				t = 1;
			dx = p[0] + t * pqx - pt[0];
			dz = p[2] + t * pqz - pt[2];
			return dx * dx + dz * dz;
		}
		public static float distToTriMesh(float* p, float* verts, int @int, int* tris, int ntris)
		{
			float dmin = FLT_MAX;
			for (int i = 0; i < ntris; ++i)
			{
				float* va = &verts[tris[i * 4 + 0] * 3];
				float* vb = &verts[tris[i * 4 + 1] * 3];
				float* vc = &verts[tris[i * 4 + 2] * 3];
				float d = distPtTri(p, va, vb, vc);
				if (d < dmin)
					dmin = d;
			}
			if (dmin == FLT_MAX) return -1;
			return dmin;
		}
		public static float distToPoly(int nvert, float* verts, float* p)
		{
			float dmin = FLT_MAX;
			int i, j, c = 0;
			for (i = 0, j = nvert - 1; i < nvert; j = i++)
			{
				float* vi = &verts[i * 3];
				float* vj = &verts[j * 3];
				if (((vi[2] > p[2]) != (vj[2] > p[2])) &&
				(p[0] < (vj[0] - vi[0]) * (p[2] - vi[2]) / (vj[2] - vi[2]) + vi[0]))
					c = -c;//!c;
				dmin = rcMin(dmin, distancePtSeg2d(p, vj, vi));
			}
			return 0 != c ? -dmin : dmin;
		}
		public static ushort getHeight(float fx, float fy, float fz, float @float, float ics, float ch, int radius, ref rcHeightPatch hp)
		{
			int ix = (int)floorf(fx * ics + 0.01f);
			int iz = (int)floorf(fz * ics + 0.01f);
			ix = rcClamp(ix - hp.xmin, 0, hp.width - 1);
			iz = rcClamp(iz - hp.ymin, 0, hp.height - 1);
			ushort h = hp.data[ix + iz * hp.width];
			if (h == RC_UNSET_HEIGHT)
			{
				int x = 1, z = 0, dx = 1, dz = 0;
				int maxSize = radius * 2 + 1;
				int maxIter = maxSize * maxSize - 1;
				int nextRingIterStart = 8;
				int nextRingIters = 16;
				float dmin = FLT_MAX;
				for (int i = 0; i < maxIter; i++)
				{
					int nx = ix + x;
					int nz = iz + z;
					if (nx >= 0 && nz >= 0 && nx < hp.width && nz < hp.height)
					{
						ushort nh = hp.data[nx + nz * hp.width];
						if (nh != RC_UNSET_HEIGHT)
						{
							float d = fabsf(nh * ch - fy);
							if (d < dmin)
							{
								h = nh;
								dmin = d;
							}
						}
					}
					if (i + 1 == nextRingIterStart)
					{
						if (h != RC_UNSET_HEIGHT)
							break;
						nextRingIterStart += nextRingIters;
						nextRingIters += 8;
					}
					if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
					{
						int tmp = dx;
						dx = -dz;
						dz = tmp;
					}
					x += dx;
					z += dz;
				}
			}
			return h;
		}
		public static int findEdge(int* edges, int nedges, int s, int t)
		{
			for (int i = 0; i < nedges; i++)
			{
				int* e = &edges[i * 4];
				if ((e[0] == s && e[1] == t) || (e[0] == t && e[1] == s))
					return i;
			}
			return (int)EdgeValues.EV_UNDEF;
		}
		public static int addEdge(rcContext* ctx, int* edges, ref int nedges, int maxEdges, int s, int t, int l, int r)
		{
			if (nedges >= maxEdges)
			{
				//ctx->log(RC_LOG_ERROR, "addEdge: Too many edges (%d/%d).", nedges, maxEdges);
				return (int)EdgeValues.EV_UNDEF;
			}
			int e = findEdge(edges, nedges, s, t);
			if (e == (int)EdgeValues.EV_UNDEF)
			{
				int* edge = &edges[nedges * 4];
				edge[0] = s;
				edge[1] = t;
				edge[2] = l;
				edge[3] = r;
				return nedges++;
			}
			else
			{
				return (int)EdgeValues.EV_UNDEF;
			}
		}
		public static void updateLeftFace(int* e, int s, int t, int f)
		{
			if (e[0] == s && e[1] == t && e[2] == (int)EdgeValues.EV_UNDEF)
				e[2] = f;
			else if (e[1] == s && e[0] == t && e[3] == (int)EdgeValues.EV_UNDEF)
				e[3] = f;
		}
		public static int overlapSegSeg2d(float* a, float* b, float* c, float* d)
		{
			float a1 = vcross2(a, b, d);
			float a2 = vcross2(a, b, c);
			if (a1 * a2 < 0.0f)
			{
				float a3 = vcross2(c, d, a);
				float a4 = a3 + a2 - a1;
				if (a3 * a4 < 0.0f)
					return 1;
			}
			return 0;
		}
		public static bool overlapEdges(float* pts, int* edges, int nedges, int s1, int t1)
		{
			for (int i = 0; i < nedges; ++i)
			{
				int s0 = edges[i * 4 + 0];
				int t0 = edges[i * 4 + 1];
				if (s0 == s1 || s0 == t1 || t0 == s1 || t0 == t1)
					continue;
				if (0 != overlapSegSeg2d(&pts[s0 * 3], &pts[t0 * 3], &pts[s1 * 3], &pts[t1 * 3]))
					return true;
			}
			return false;
		}
		public static void completeFacet(rcContext* ctx, float* pts, int npts, int* edges, ref int nedges, int maxEdges, ref int nfaces, int e)
		{
			float EPS = 1e-5f;
			int* edge = &edges[e * 4];
			int s, t;
			if (edge[2] == (int)EdgeValues.EV_UNDEF)
			{
				s = edge[0];
				t = edge[1];
			}
			else if (edge[3] == (int)EdgeValues.EV_UNDEF)
			{
				s = edge[1];
				t = edge[0];
			}
			else
			{

				return;
			}

			int pt = npts;
			float* c = stackalloc float[3] { 0, 0, 0 };
			float r = -1;
			for (int u = 0; u < npts; ++u)
			{
				if (u == s || u == t) continue;
				if (vcross2(&pts[s * 3], &pts[t * 3], &pts[u * 3]) > EPS)
				{
					if (r < 0)
					{
						pt = u;
						circumCircle(&pts[s * 3], &pts[t * 3], &pts[u * 3], c, ref r);
						continue;
					}
					float d = vdist2(c, &pts[u * 3]);
					float tol = 0.001f;
					if (d > r * (1 + tol))
					{
						continue;
					}
					else if (d < r * (1 - tol))
					{
						pt = u;
						circumCircle(&pts[s * 3], &pts[t * 3], &pts[u * 3], c, ref r);
					}
					else
					{
						if (overlapEdges(pts, edges, nedges, s, u))
							continue;
						if (overlapEdges(pts, edges, nedges, t, u))
							continue;
						pt = u;
						circumCircle(&pts[s * 3], &pts[t * 3], &pts[u * 3], c, ref r);
					}
				}
			}
			if (pt < npts)
			{
				updateLeftFace(&edges[e * 4], s, t, nfaces);
				e = findEdge(edges, nedges, pt, s);
				if (e == (int)EdgeValues.EV_UNDEF)
					addEdge(ctx, edges, ref nedges, maxEdges, pt, s, nfaces, (int)EdgeValues.EV_UNDEF);
				else
					updateLeftFace(&edges[e * 4], pt, s, nfaces);
				e = findEdge(edges, nedges, t, pt);
				if (e == (int)EdgeValues.EV_UNDEF)
					addEdge(ctx, edges, ref nedges, maxEdges, t, pt, nfaces, (int)EdgeValues.EV_UNDEF);
				else
					updateLeftFace(&edges[e * 4], t, pt, nfaces);
				nfaces++;
			}
			else
			{
				updateLeftFace(&edges[e * 4], s, t, (int)EdgeValues.EV_HULL);
			}
		}
		public static void delaunayHull(rcContext* ctx, int npts, float* pts, int nhull, int* hull, ref rcIntArray tris, ref rcIntArray edges)
		{
			int nfaces = 0;
			int nedges = 0;
			int maxEdges = npts * 10;
			edges.resize(maxEdges * 4);
			for (int i = 0, j = nhull - 1; i < nhull; j = i++)
				addEdge(ctx, &edges.Data[0], ref nedges, maxEdges, hull[j], hull[i], (int)EdgeValues.EV_HULL, (int)EdgeValues.EV_UNDEF);
			int currentEdge = 0;
			while (currentEdge < nedges)
			{
				if (edges[currentEdge * 4 + 2] == (int)EdgeValues.EV_UNDEF)
					completeFacet(ctx, pts, npts, &edges.Data[0], ref nedges, maxEdges, ref nfaces, currentEdge);
				if (edges[currentEdge * 4 + 3] == (int)EdgeValues.EV_UNDEF)
					completeFacet(ctx, pts, npts, &edges.Data[0], ref nedges, maxEdges, ref nfaces, currentEdge);
				currentEdge++;
			}
			tris.resize(nfaces * 4);
			for (int i = 0; i < nfaces * 4; ++i)
				tris[i] = -1;
			for (int i = 0; i < nedges; ++i)
			{
				int* e = &edges.Data[i * 4];
				if (e[3] >= 0)
				{
					int* t = &tris.Data[e[3] * 4];
					if (t[0] == -1)
					{
						t[0] = e[0];
						t[1] = e[1];
					}
					else if (t[0] == e[1])
						t[2] = e[0];
					else if (t[1] == e[0])
						t[2] = e[1];
				}
				if (e[2] >= 0)
				{
					int* t = &tris.Data[e[2] * 4];
					if (t[0] == -1)
					{
						t[0] = e[1];
						t[1] = e[0];
					}
					else if (t[0] == e[0])
						t[2] = e[1];
					else if (t[1] == e[1])
						t[2] = e[0];
				}
			}
			for (int i = 0; i < tris.size() / 4; ++i)
			{
				int* t = &tris.Data[i * 4];
				if (t[0] == -1 || t[1] == -1 || t[2] == -1)
				{
					//ctx->log(RC_LOG_WARNING, "delaunayHull: Removing dangling face %d [%d,%d,%d].", i, t[0], t[1], t[2]);
					t[0] = tris[tris.size() - 4];
					t[1] = tris[tris.size() - 3];
					t[2] = tris[tris.size() - 2];
					t[3] = tris[tris.size() - 1];
					tris.resize(tris.size() - 4);
					--i;
				}
			}
		}
		public static float polyMinExtent(float* verts, int nverts)
		{
			float minDist = FLT_MAX;
			for (int i = 0; i < nverts; i++)
			{
				int ni = (i + 1) % nverts;
				float* p1 = &verts[i * 3];
				float* p2 = &verts[ni * 3];
				float maxEdgeDist = 0;
				for (int j = 0; j < nverts; j++)
				{
					if (j == i || j == ni) continue;
					float d = distancePtSeg2d(&verts[j * 3], p1, p2);
					maxEdgeDist = rcMax(maxEdgeDist, d);
				}
				minDist = rcMin(minDist, maxEdgeDist);
			}
			return rcSqrt(minDist);
		}
		public static void triangulateHull(int @int, float* verts, int nhull, int* hull, int nin, ref rcIntArray tris)
		{
			int start = 0, left = 1, right = nhull - 1;
			float dmin = FLT_MAX;
			for (int i = 0; i < nhull; i++)
			{
				if (hull[i] >= nin) continue;
				int pi = prev(i, nhull);
				int ni = next(i, nhull);
				float* pv = &verts[hull[pi] * 3];
				float* cv = &verts[hull[i] * 3];
				float* nv = &verts[hull[ni] * 3];
				float d = vdist2(pv, cv) + vdist2(cv, nv) + vdist2(nv, pv);
				if (d < dmin)
				{
					start = i;
					left = ni;
					right = pi;
					dmin = d;
				}
			}
			tris.push(hull[start]);
			tris.push(hull[left]);
			tris.push(hull[right]);
			tris.push(0);
			while (next(left, nhull) != right)
			{
				int nleft = next(left, nhull);
				int nright = prev(right, nhull);
				float* cvleft = &verts[hull[left] * 3];
				float* nvleft = &verts[hull[nleft] * 3];
				float* cvright = &verts[hull[right] * 3];
				float* nvright = &verts[hull[nright] * 3];
				float dleft = vdist2(cvleft, nvleft) + vdist2(nvleft, cvright);
				float dright = vdist2(cvright, nvright) + vdist2(cvleft, nvright);
				if (dleft < dright)
				{
					tris.push(hull[left]);
					tris.push(hull[nleft]);
					tris.push(hull[right]);
					tris.push(0);
					left = nleft;
				}
				else
				{
					tris.push(hull[left]);
					tris.push(hull[nright]);
					tris.push(hull[right]);
					tris.push(0);
					right = nright;
				}
			}
		}
		public static float getJitterX(int i)
		{
			return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
		}
		public static float getJitterY(int i)
		{
			return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
		}
		const int MAX_VERTS_PER_EDGE = 32;
		public static bool buildPolyDetail(rcContext* ctx, float* @in, int nin, float sampleDist, float sampleMaxError, int heightSearchRadius, ref rcCompactHeightfield chf, ref rcHeightPatch hp, float* verts, ref int nverts, ref rcIntArray tris, ref rcIntArray edges, ref rcIntArray samples)
		{
			int MAX_VERTS = 127;
			int MAX_TRIS = 255;
			//int MAX_VERTS_PER_EDGE = 32;
			float* edge = stackalloc float[(MAX_VERTS_PER_EDGE + 1) * 3];
			int* hull = stackalloc int[MAX_VERTS];
			int nhull = 0;
			nverts = nin;
			for (int i = 0; i < nin; ++i)
				rcVcopy(&verts[i * 3], &@in[i * 3]);
			edges.clear();
			tris.clear();
			float cs = chf.cs;
			float ics = 1.0f / cs;
			float minExtent = polyMinExtent(verts, nverts);
			int* idx = stackalloc int[MAX_VERTS_PER_EDGE];
			if (sampleDist > 0)
			{
				for (int i = 0, j = nin - 1; i < nin; j = i++)
				{
					float* vj = &@in[j * 3];
					float* vi = &@in[i * 3];
					bool swapped = false;
					if (fabsf(vj[0] - vi[0]) < 1e-6f)
					{
						if (vj[2] > vi[2])
						{
							rcSwap(ref vj, ref vi);
							swapped = true;
						}
					}
					else
					{
						if (vj[0] > vi[0])
						{
							rcSwap(ref vj, ref vi);
							swapped = true;
						}
					}
					float dx = vi[0] - vj[0];
					float dy = vi[1] - vj[1];
					float dz = vi[2] - vj[2];
					float d = sqrtf(dx * dx + dz * dz);
					int nn = 1 + (int)floorf(d / sampleDist);
					if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE - 1;
					if (nverts + nn >= MAX_VERTS)
						nn = MAX_VERTS - 1 - nverts;
					for (int k = 0; k <= nn; ++k)
					{
						float u = k / (float)nn;
						float* pos = &edge[k * 3];
						pos[0] = vj[0] + dx * u;
						pos[1] = vj[1] + dy * u;
						pos[2] = vj[2] + dz * u;
						pos[1] = getHeight(pos[0], pos[1], pos[2], cs, ics, chf.ch, heightSearchRadius, ref hp) * chf.ch;
					}
					//int* idx = stackalloc int[MAX_VERTS_PER_EDGE] { 0, nn };
					memset(idx, 0, sizeof(int) * MAX_VERTS_PER_EDGE);
					idx[0] = 0;
					idx[1] = nn;
					int nidx = 2;
					for (int k = 0; k < nidx - 1;)
					{
						int a = idx[k];
						int b = idx[k + 1];
						float* va = &edge[a * 3];
						float* vb = &edge[b * 3];
						float maxd = 0;
						int maxi = -1;
						for (int m = a + 1; m < b; ++m)
						{
							float dev = distancePtSeg(&edge[m * 3], va, vb);
							if (dev > maxd)
							{
								maxd = dev;
								maxi = m;
							}
						}
						if (maxi != -1 && maxd > rcSqr(sampleMaxError))
						{
							for (int m = nidx; m > k; --m)
								idx[m] = idx[m - 1];
							idx[k + 1] = maxi;
							nidx++;
						}
						else
						{
							++k;
						}
					}
					hull[nhull++] = j;
					if (swapped)
					{
						for (int k = nidx - 2; k > 0; --k)
						{
							rcVcopy(&verts[nverts * 3], &edge[idx[k] * 3]);
							hull[nhull++] = nverts;
							nverts++;
						}
					}
					else
					{
						for (int k = 1; k < nidx - 1; ++k)
						{
							rcVcopy(&verts[nverts * 3], &edge[idx[k] * 3]);
							hull[nhull++] = nverts;
							nverts++;
						}
					}
				}
			}
			if (minExtent < sampleDist * 2)
			{
				triangulateHull(nverts, verts, nhull, hull, nin, ref tris);
				return true;
			}
			triangulateHull(nverts, verts, nhull, hull, nin, ref tris);
			if (tris.size() == 0)
			{
				//ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon (%d verts).", nverts);
				return true;
			}
			if (sampleDist > 0)
			{
				float* bmin = stackalloc float[3], bmax = stackalloc float[3];
				rcVcopy(bmin, @in);
				rcVcopy(bmax, @in);
				for (int i = 1; i < nin; ++i)
				{
					rcVmin(bmin, &@in[i * 3]);
					rcVmax(bmax, &@in[i * 3]);
				}
				int x0 = (int)floorf(bmin[0] / sampleDist);
				int x1 = (int)ceilf(bmax[0] / sampleDist);
				int z0 = (int)floorf(bmin[2] / sampleDist);
				int z1 = (int)ceilf(bmax[2] / sampleDist);
				samples.clear();
				float* pt = stackalloc float[3];
				for (int z = z0; z < z1; ++z)
				{
					for (int x = x0; x < x1; ++x)
					{
						//float* pt = stackalloc float[3];
						memset(pt, 0, sizeof(float) * 3);
						pt[0] = x * sampleDist;
						pt[1] = (bmax[1] + bmin[1]) * 0.5f;
						pt[2] = z * sampleDist;
						if (distToPoly(nin, @in, pt) > -sampleDist / 2) continue;
						samples.push(x);
						samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, heightSearchRadius, ref hp));
						samples.push(z);
						samples.push(0);
					}
				}
				int nsamples = samples.size() / 4;
				float* bestpt = stackalloc float[3];
				//float* pt = stackalloc float[3];
				for (int iter = 0; iter < nsamples; ++iter)
				{
					if (nverts >= MAX_VERTS)
						break;
					//float* bestpt = stackalloc float[3] { 0, 0, 0 };
					memset(bestpt, 0, sizeof(float) * 3);
					float bestd = 0;
					int besti = -1;
					for (int i = 0; i < nsamples; ++i)
					{
						int* s = &samples.Data[i * 4];
						if (0 != s[3]) continue;
						//float* pt = stackalloc float[3];
						memset(pt, 0, sizeof(float) * 3);
						pt[0] = s[0] * sampleDist + getJitterX(i) * cs * 0.1f;
						pt[1] = s[1] * chf.ch;
						pt[2] = s[2] * sampleDist + getJitterY(i) * cs * 0.1f;
						float d = distToTriMesh(pt, verts, nverts, &tris.Data[0], tris.size() / 4);
						if (d < 0) continue;
						if (d > bestd)
						{
							bestd = d;
							besti = i;
							rcVcopy(bestpt, pt);
						}
					}
					if (bestd <= sampleMaxError || besti == -1)
						break;
					samples[besti * 4 + 3] = 1;
					rcVcopy(&verts[nverts * 3], bestpt);
					nverts++;
					edges.clear();
					tris.clear();
					delaunayHull(ctx, nverts, verts, nhull, hull, ref tris, ref edges);
				}
			}
			int ntris = tris.size() / 4;
			if (ntris > MAX_TRIS)
			{
				tris.resize(MAX_TRIS * 4);
				//ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS);
			}
			return true;
		}
		public static void seedArrayWithPolyCenter(rcContext* ctx, ref rcCompactHeightfield chf, ushort* poly, int npoly, ushort* verts, int bs, ref rcHeightPatch hp, ref rcIntArray array)
		{
			int* offset = stackalloc int[9 * 2]
			{
				0,
				0,
				-1,
				-1,
				0,
				-1,
				1,
				-1,
				1,
				0,
				1,
				1,
				0,
				1,
				-1,
				1,
				-1,
				0,
			};
			int startCellX = 0, startCellY = 0, startSpanIndex = -1;
			int dmin = (int)RC_UNSET_HEIGHT;
			for (int j = 0; j < npoly && dmin > 0; ++j)
			{
				for (int k = 0; k < 9 && dmin > 0; ++k)
				{
					int ax = (int)verts[poly[j] * 3 + 0] + offset[k * 2 + 0];
					int ay = (int)verts[poly[j] * 3 + 1];
					int az = (int)verts[poly[j] * 3 + 2] + offset[k * 2 + 1];
					if (ax < hp.xmin || ax >= hp.xmin + hp.width ||
					az < hp.ymin || az >= hp.ymin + hp.height)
						continue;
					ref rcCompactCell c = ref chf.cells[(ax + bs) + (az + bs) * chf.width];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni && dmin > 0; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						int d = rcAbs(ay - s.y);
						if (d < dmin)
						{
							startCellX = ax;
							startCellY = az;
							startSpanIndex = i;
							dmin = d;
						}
					}
				}
			}
			rcAssert(startSpanIndex != -1);
			int pcx = 0, pcy = 0;
			for (int j = 0; j < npoly; ++j)
			{
				pcx += (int)verts[poly[j] * 3 + 0];
				pcy += (int)verts[poly[j] * 3 + 2];
			}
			pcx /= npoly;
			pcy /= npoly;
			array.clear();
			array.push(startCellX);
			array.push(startCellY);
			array.push(startSpanIndex);
			int* dirs = stackalloc int[] { 0, 1, 2, 3 };
			memset(hp.data, 0, sizeof(ushort) * hp.width * hp.height);
			int cx = -1, cy = -1, ci = -1;
			while (true)
			{
				if (array.size() < 3)
				{
					//ctx->log(RC_LOG_WARNING, "Walk towards polygon center failed to reach center");
					break;
				}
				ci = array.pop();
				cy = array.pop();
				cx = array.pop();
				if (cx == pcx && cy == pcy)
					break;
				int directDir;
				if (cx == pcx)
					directDir = rcGetDirForOffset(0, pcy > cy ? 1 : -1);
				else
					directDir = rcGetDirForOffset(pcx > cx ? 1 : -1, 0);
				rcSwap(ref dirs[directDir], ref dirs[3]);
				ref rcCompactSpan cs1 = ref chf.spans[ci];
				for (int i = 0; i < 4; i++)
				{
					int dir = dirs[i];
					if (rcGetCon(ref cs1, dir) == RC_NOT_CONNECTED)
						continue;
					int newX = cx + rcGetDirOffsetX(dir);
					int newY = cy + rcGetDirOffsetY(dir);
					int hpx = newX - hp.xmin;
					int hpy = newY - hp.ymin;
					if (hpx < 0 || hpx >= hp.width || hpy < 0 || hpy >= hp.height)
						continue;
					if (hp.data[hpx + hpy * hp.width] != 0)
						continue;
					hp.data[hpx + hpy * hp.width] = 1;
					array.push(newX);
					array.push(newY);
					array.push((int)chf.cells[(newX + bs) + (newY + bs) * chf.width].index + rcGetCon(ref cs1, dir));
				}
				rcSwap(ref dirs[directDir], ref dirs[3]);
			}
			array.clear();
			array.push(cx + bs);
			array.push(cy + bs);
			array.push(ci);
			memset(hp.data, 0xff, sizeof(ushort) * hp.width * hp.height);
			ref rcCompactSpan cs = ref chf.spans[ci];
			hp.data[cx - hp.xmin + (cy - hp.ymin) * hp.width] = cs.y;
		}
		public static void push3(ref rcIntArray queue, int v1, int v2, int v3)
		{
			queue.resize(queue.size() + 3);
			queue[queue.size() - 3] = v1;
			queue[queue.size() - 2] = v2;
			queue[queue.size() - 1] = v3;
		}
		public static void getHeightData(rcContext* ctx, ref rcCompactHeightfield chf, ushort* poly, int npoly, ushort* verts, int bs, ref rcHeightPatch hp, ref rcIntArray queue, int region)
		{
			queue.clear();
			memset(hp.data, 0xff, sizeof(ushort) * hp.width * hp.height);
			bool empty = true;
			if (region != RC_MULTIPLE_REGS)
			{
				for (int hy = 0; hy < hp.height; hy++)
				{
					int y = hp.ymin + hy + bs;
					for (int hx = 0; hx < hp.width; hx++)
					{
						int x = hp.xmin + hx + bs;
						ref rcCompactCell c = ref chf.cells[x + y * chf.width];
						for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
						{
							ref rcCompactSpan s = ref chf.spans[i];
							if (s.reg == region)
							{
								hp.data[hx + hy * hp.width] = s.y;
								empty = false;
								bool border = false;
								for (int dir = 0; dir < 4; ++dir)
								{
									if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
									{
										int ax = x + rcGetDirOffsetX(dir);
										int ay = y + rcGetDirOffsetY(dir);
										int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref s, dir);
										ref rcCompactSpan as1 = ref chf.spans[ai];
										if (as1.reg != region)
										{
											border = true;
											break;
										}
									}
								}
								if (border)
									push3(ref queue, x, y, i);
								break;
							}
						}
					}
				}
			}
			if (empty)
				seedArrayWithPolyCenter(ctx, ref chf, poly, npoly, verts, bs, ref hp, ref queue);
			int RETRACT_SIZE = 256;
			int head = 0;
			while (head * 3 < queue.size())
			{
				int cx = queue[head * 3 + 0];
				int cy = queue[head * 3 + 1];
				int ci = queue[head * 3 + 2];
				head++;
				if (head >= RETRACT_SIZE)
				{
					head = 0;
					if (queue.size() > RETRACT_SIZE * 3)
						memmove(&queue.Data[0], &queue.Data[RETRACT_SIZE * 3], sizeof(int) * (queue.size() - RETRACT_SIZE * 3));
					queue.resize(queue.size() - RETRACT_SIZE * 3);
				}
				ref rcCompactSpan cs = ref chf.spans[ci];
				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(ref cs, dir) == RC_NOT_CONNECTED) continue;
					int ax = cx + rcGetDirOffsetX(dir);
					int ay = cy + rcGetDirOffsetY(dir);
					int hx = ax - hp.xmin - bs;
					int hy = ay - hp.ymin - bs;
					if ((uint)hx >= (uint)hp.width || (uint)hy >= (uint)hp.height)
						continue;
					if (hp.data[hx + hy * hp.width] != RC_UNSET_HEIGHT)
						continue;
					int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref cs, dir);
					ref rcCompactSpan as1 = ref chf.spans[ai];
					hp.data[hx + hy * hp.width] = as1.y;
					push3(ref queue, ax, ay, ai);
				}
			}
		}
		public static byte getEdgeFlags(float* va, float* vb, float* vpoly, int npoly)
		{
			float thrSqr = rcSqr(0.001f);
			for (int i = 0, j = npoly - 1; i < npoly; j = i++)
			{
				if (distancePtSeg2d(va, &vpoly[j * 3], &vpoly[i * 3]) < thrSqr &&
				distancePtSeg2d(vb, &vpoly[j * 3], &vpoly[i * 3]) < thrSqr)
					return 1;
			}
			return 0;
		}
		public static byte getTriFlags(float* va, float* vb, float* vc, float* vpoly, int npoly)
		{
			byte flags = 0;
			flags = (byte)(flags | getEdgeFlags(va, vb, vpoly, npoly) << 0);
			flags = (byte)(flags | getEdgeFlags(vb, vc, vpoly, npoly) << 2);
			flags = (byte)(flags | getEdgeFlags(vc, va, vpoly, npoly) << 4);
			return flags;
		}
		public static bool overlapBounds(float* aMin, float* aMax, float* bMin, float* bMax)
		{
			return
			aMin[0] <= bMax[0] && aMax[0] >= bMin[0] &&
			aMin[1] <= bMax[1] && aMax[1] >= bMin[1] &&
			aMin[2] <= bMax[2] && aMax[2] >= bMin[2];
		}
		public static rcSpan* allocSpan(ref rcHeightfield heightfield)
		{
			if (heightfield.freelist == null || heightfield.freelist->next == null)
			{
				rcSpanPool* spanPool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), rcAllocHint.RC_ALLOC_PERM);
				*spanPool = new rcSpanPool(0);
				if (spanPool == null)
				{
					return null;
				}
				spanPool->next = heightfield.pools;
				heightfield.pools = spanPool;
				rcSpan* freeList = heightfield.freelist;
				rcSpan* head = &spanPool->items[0];
				rcSpan* it = &spanPool->items[RC_SPANS_PER_POOL];
				do
				{
					--it;
					it->next = freeList;
					freeList = it;
				}
				while (it != head);
				heightfield.freelist = it;
			}
			rcSpan* newSpan = heightfield.freelist;
			heightfield.freelist = heightfield.freelist->next;
			return newSpan;
		}
		public static void freeSpan(ref rcHeightfield heightfield, rcSpan* span)
		{
			if (span == null)
			{
				return;
			}
			span->next = heightfield.freelist;
			heightfield.freelist = span;
		}
		public static bool addSpan(ref rcHeightfield heightfield, int x, int z, ushort min, ushort max, byte areaID, int flagMergeThreshold)
		{
			rcSpan* newSpan = allocSpan(ref heightfield);
			if (newSpan == null)
			{
				return false;
			}
			newSpan->smin = min;
			newSpan->smax = max;
			newSpan->area = areaID;
			newSpan->next = null;
			int columnIndex = x + z * heightfield.width;
			rcSpan* previousSpan = null;
			rcSpan* currentSpan = heightfield.spans[columnIndex];
			while (currentSpan != null)
			{
				if (currentSpan->smin > newSpan->smax)
				{
					break;
				}
				if (currentSpan->smax < newSpan->smin)
				{
					previousSpan = currentSpan;
					currentSpan = currentSpan->next;
				}
				else
				{
					if (currentSpan->smin < newSpan->smin)
					{
						newSpan->smin = currentSpan->smin;
					}
					if (currentSpan->smax > newSpan->smax)
					{
						newSpan->smax = currentSpan->smax;
					}
					if (rcAbs((int)newSpan->smax - (int)currentSpan->smax) <= flagMergeThreshold)
					{
						newSpan->area = rcMax(newSpan->area, currentSpan->area);
					}
					rcSpan* next = currentSpan->next;
					freeSpan(ref heightfield, currentSpan);
					if (previousSpan != null)
					{
						previousSpan->next = next;
					}
					else
					{
						heightfield.spans[columnIndex] = next;
					}
					currentSpan = next;
				}
			}
			if (previousSpan != null)
			{
				newSpan->next = previousSpan->next;
				previousSpan->next = newSpan;
			}
			else
			{
				newSpan->next = heightfield.spans[columnIndex];
				heightfield.spans[columnIndex] = newSpan;
			}
			return true;
		}
		public static void dividePoly(float* inVerts, int inVertsCount, float* OutVerts1, int* OutVerts1Count, float* OutVerts2, int* OutVerts2Count, float axisOffset, rcAxis axis)
		{
			rcAssert(inVertsCount <= 12);
			float* inVertAxisDelta = stackalloc float[12];
			for (int inVert = 0; inVert < inVertsCount; ++inVert)
			{
				inVertAxisDelta[inVert] = axisOffset - inVerts[(int)(inVert * 3 + axis)];
			}
			int poly1Vert = 0;
			int poly2Vert = 0;
			for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA)
			{
				bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0);
				if (!sameSide)
				{
					float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]);
					OutVerts1[poly1Vert * 3 + 0] = inVerts[inVertB * 3 + 0] + (inVerts[inVertA * 3 + 0] - inVerts[inVertB * 3 + 0]) * s;
					OutVerts1[poly1Vert * 3 + 1] = inVerts[inVertB * 3 + 1] + (inVerts[inVertA * 3 + 1] - inVerts[inVertB * 3 + 1]) * s;
					OutVerts1[poly1Vert * 3 + 2] = inVerts[inVertB * 3 + 2] + (inVerts[inVertA * 3 + 2] - inVerts[inVertB * 3 + 2]) * s;
					rcVcopy(&OutVerts2[poly2Vert * 3], &OutVerts1[poly1Vert * 3]);
					poly1Vert++;
					poly2Vert++;
					if (inVertAxisDelta[inVertA] > 0)
					{
						rcVcopy(&OutVerts1[poly1Vert * 3], &inVerts[inVertA * 3]);
						poly1Vert++;
					}
					else if (inVertAxisDelta[inVertA] < 0)
					{
						rcVcopy(&OutVerts2[poly2Vert * 3], &inVerts[inVertA * 3]);
						poly2Vert++;
					}
				}
				else
				{
					if (inVertAxisDelta[inVertA] >= 0)
					{
						rcVcopy(&OutVerts1[poly1Vert * 3], &inVerts[inVertA * 3]);
						poly1Vert++;
						if (inVertAxisDelta[inVertA] != 0)
						{
							continue;
						}
					}
					rcVcopy(&OutVerts2[poly2Vert * 3], &inVerts[inVertA * 3]);
					poly2Vert++;
				}
			}
			*OutVerts1Count = poly1Vert;
			*OutVerts2Count = poly2Vert;
		}
		public static bool rasterizeTri(float* v0, float* v1, float* v2, byte areaID, ref rcHeightfield heightfield, float* heightfieldBBMin, float* heightfieldBBMax, float cellSize, float inverseCellSize, float inverseCellHeight, int flagMergeThreshold)
		{
			float* triBBMin = stackalloc float[3];
			rcVcopy(triBBMin, v0);
			rcVmin(triBBMin, v1);
			rcVmin(triBBMin, v2);
			float* triBBMax = stackalloc float[3];
			rcVcopy(triBBMax, v0);
			rcVmax(triBBMax, v1);
			rcVmax(triBBMax, v2);
			if (!overlapBounds(triBBMin, triBBMax, heightfieldBBMin, heightfieldBBMax))
			{
				return true;
			}
			int w = heightfield.width;
			int h = heightfield.height;
			float by = heightfieldBBMax[1] - heightfieldBBMin[1];
			int z0 = (int)((triBBMin[2] - heightfieldBBMin[2]) * inverseCellSize);
			int z1 = (int)((triBBMax[2] - heightfieldBBMin[2]) * inverseCellSize);
			z0 = rcClamp(z0, -1, h - 1);
			z1 = rcClamp(z1, 0, h - 1);
			float* buf = stackalloc float[7 * 3 * 4];
			float* @in = buf;
			float* inRow = buf + 7 * 3;
			float* p1 = inRow + 7 * 3;
			float* p2 = p1 + 7 * 3;
			rcVcopy(&@in[0], v0);
			rcVcopy(&@in[1 * 3], v1);
			rcVcopy(&@in[2 * 3], v2);
			int nvRow;
			int nvIn = 3;
			for (int z = z0; z <= z1; ++z)
			{
				float cellZ = heightfieldBBMin[2] + (float)z * cellSize;
				dividePoly(@in, nvIn, inRow, &nvRow, p1, &nvIn, cellZ + cellSize, rcAxis.RC_AXIS_Z);
				rcSwap(ref @in, ref p1);
				if (nvRow < 3)
				{
					continue;
				}
				if (z < 0)
				{
					continue;
				}
				float minX = inRow[0];
				float maxX = inRow[0];
				for (int vert = 1; vert < nvRow; ++vert)
				{
					if (minX > inRow[vert * 3])
					{
						minX = inRow[vert * 3];
					}
					if (maxX < inRow[vert * 3])
					{
						maxX = inRow[vert * 3];
					}
				}
				int x0 = (int)((minX - heightfieldBBMin[0]) * inverseCellSize);
				int x1 = (int)((maxX - heightfieldBBMin[0]) * inverseCellSize);
				if (x1 < 0 || x0 >= w)
				{
					continue;
				}
				x0 = rcClamp(x0, -1, w - 1);
				x1 = rcClamp(x1, 0, w - 1);
				int nv;
				int nv2 = nvRow;
				for (int x = x0; x <= x1; ++x)
				{
					float cx = heightfieldBBMin[0] + (float)x * cellSize;
					dividePoly(inRow, nv2, p1, &nv, p2, &nv2, cx + cellSize, rcAxis.RC_AXIS_X);
					rcSwap(ref inRow, ref p2);
					if (nv < 3)
					{
						continue;
					}
					if (x < 0)
					{
						continue;
					}
					float spanMin = p1[1];
					float spanMax = p1[1];
					for (int vert = 1; vert < nv; ++vert)
					{
						spanMin = rcMin(spanMin, p1[vert * 3 + 1]);
						spanMax = rcMax(spanMax, p1[vert * 3 + 1]);
					}
					spanMin -= heightfieldBBMin[1];
					spanMax -= heightfieldBBMin[1];
					if (spanMax < 0.0f)
					{
						continue;
					}
					if (spanMin > by)
					{
						continue;
					}
					if (spanMin < 0.0f)
					{
						spanMin = 0;
					}
					if (spanMax > by)
					{
						spanMax = by;
					}
					ushort spanMinCellIndex = (ushort)rcClamp((int)floorf(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT);
					ushort spanMaxCellIndex = (ushort)rcClamp((int)ceilf(spanMax * inverseCellHeight), (int)spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT);
					if (!addSpan(ref heightfield, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold))
					{
						return false;
					}
				}
			}
			return true;
		}
		public static bool rcRasterizeTriangle(rcContext* context, float* v0, float* v1, float* v2, byte areaID, ref rcHeightfield heightfield, int flagMergeThreshold)
		{
			rcAssert(context != null);
			//rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
			float inverseCellSize = 1.0f / heightfield.cs;
			float inverseCellHeight = 1.0f / heightfield.ch;
			rcHeightfield* heightfield_ptr = (rcHeightfield*)Unsafe.AsPointer(ref heightfield);
			if (!rasterizeTri(v0, v1, v2, areaID, ref heightfield, heightfield_ptr->bmin, heightfield_ptr->bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
			{
				return false;
			}
			return true;
		}
		public static bool rcRasterizeTriangles(rcContext* context, float* verts, int @int, int* tris, byte* triAreaIDs, int numTris, ref rcHeightfield heightfield, int flagMergeThreshold)
		{
			rcAssert(context != null);
			//rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
			float inverseCellSize = 1.0f / heightfield.cs;
			float inverseCellHeight = 1.0f / heightfield.ch;
			rcHeightfield* heightfield_ptr = (rcHeightfield*)Unsafe.AsPointer(ref heightfield);
			for (int triIndex = 0; triIndex < numTris; ++triIndex)
			{
				float* v0 = &verts[tris[triIndex * 3 + 0] * 3];
				float* v1 = &verts[tris[triIndex * 3 + 1] * 3];
				float* v2 = &verts[tris[triIndex * 3 + 2] * 3];
				if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], ref heightfield, heightfield_ptr->bmin, heightfield_ptr->bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
				{
					return false;
				}
			}
			return true;
		}
		public static bool rcRasterizeTriangles(rcContext* context, float* verts, int @int, ushort* tris, byte* triAreaIDs, int numTris, ref rcHeightfield heightfield, int flagMergeThreshold)
		{
			rcAssert(context != null);
			//rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
			float inverseCellSize = 1.0f / heightfield.cs;
			float inverseCellHeight = 1.0f / heightfield.ch;
			rcHeightfield* heightfield_ptr = (rcHeightfield*)Unsafe.AsPointer(ref heightfield);
			for (int triIndex = 0; triIndex < numTris; ++triIndex)
			{
				float* v0 = &verts[tris[triIndex * 3 + 0] * 3];
				float* v1 = &verts[tris[triIndex * 3 + 1] * 3];
				float* v2 = &verts[tris[triIndex * 3 + 2] * 3];
				if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], ref heightfield, heightfield_ptr->bmin, heightfield_ptr->bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
				{
					return false;
				}
			}
			return true;
		}
		public static bool rcRasterizeTriangles(rcContext* context, float* verts, byte* triAreaIDs, int numTris, ref rcHeightfield heightfield, int flagMergeThreshold)
		{
			rcAssert(context != null);
			//rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
			float inverseCellSize = 1.0f / heightfield.cs;
			float inverseCellHeight = 1.0f / heightfield.ch;
			rcHeightfield* heightfield_ptr = (rcHeightfield*)Unsafe.AsPointer(ref heightfield);
			for (int triIndex = 0; triIndex < numTris; ++triIndex)
			{
				float* v0 = &verts[(triIndex * 3 + 0) * 3];
				float* v1 = &verts[(triIndex * 3 + 1) * 3];
				float* v2 = &verts[(triIndex * 3 + 2) * 3];
				if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], ref heightfield, heightfield_ptr->bmin, heightfield_ptr->bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
				{
					return false;
				}
			}
			return true;
		}
		public static void calculateDistanceField(ref rcCompactHeightfield chf, ushort* src, ref ushort maxDist)
		{
			int w = chf.width;
			int h = chf.height;
			for (int i = 0; i < chf.spanCount; ++i)
				src[i] = 0xffff;
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						byte area = chf.areas[i];
						int nc = 0;
						for (int dir = 0; dir < 4; ++dir)
						{
							if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
							{
								int ax = x + rcGetDirOffsetX(dir);
								int ay = y + rcGetDirOffsetY(dir);
								int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
								if (area == chf.areas[ai])
									nc++;
							}
						}
						if (nc != 4)
							src[i] = 0;
					}
				}
			}
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						if (rcGetCon(ref s, 0) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(0);
							int ay = y + rcGetDirOffsetY(0);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 0);
							ref rcCompactSpan as1 = ref chf.spans[ai];
							if (src[ai] + 2 < src[i])
								src[i] = (ushort)(src[ai] + 2);
							if (rcGetCon(ref as1, 3) != RC_NOT_CONNECTED)
							{
								int aax = ax + rcGetDirOffsetX(3);
								int aay = ay + rcGetDirOffsetY(3);
								int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(ref as1, 3);
								if (src[aai] + 3 < src[i])
									src[i] = (ushort)(src[aai] + 3);
							}
						}
						if (rcGetCon(ref s, 3) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(3);
							int ay = y + rcGetDirOffsetY(3);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 3);
							ref rcCompactSpan as1 = ref chf.spans[ai];
							if (src[ai] + 2 < src[i])
								src[i] = (ushort)(src[ai] + 2);
							if (rcGetCon(ref as1, 2) != RC_NOT_CONNECTED)
							{
								int aax = ax + rcGetDirOffsetX(2);
								int aay = ay + rcGetDirOffsetY(2);
								int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(ref as1, 2);
								if (src[aai] + 3 < src[i])
									src[i] = (ushort)(src[aai] + 3);
							}
						}
					}
				}
			}
			for (int y = h - 1; y >= 0; --y)
			{
				for (int x = w - 1; x >= 0; --x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						if (rcGetCon(ref s, 2) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(2);
							int ay = y + rcGetDirOffsetY(2);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 2);
							ref rcCompactSpan as1 = ref chf.spans[ai];
							if (src[ai] + 2 < src[i])
								src[i] = (ushort)(src[ai] + 2);
							if (rcGetCon(ref as1, 1) != RC_NOT_CONNECTED)
							{
								int aax = ax + rcGetDirOffsetX(1);
								int aay = ay + rcGetDirOffsetY(1);
								int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(ref as1, 1);
								if (src[aai] + 3 < src[i])
									src[i] = (ushort)(src[aai] + 3);
							}
						}
						if (rcGetCon(ref s, 1) != RC_NOT_CONNECTED)
						{
							int ax = x + rcGetDirOffsetX(1);
							int ay = y + rcGetDirOffsetY(1);
							int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, 1);
							ref rcCompactSpan as1 = ref chf.spans[ai];
							if (src[ai] + 2 < src[i])
								src[i] = (ushort)(src[ai] + 2);
							if (rcGetCon(ref as1, 0) != RC_NOT_CONNECTED)
							{
								int aax = ax + rcGetDirOffsetX(0);
								int aay = ay + rcGetDirOffsetY(0);
								int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(ref as1, 0);
								if (src[aai] + 3 < src[i])
									src[i] = (ushort)(src[aai] + 3);
							}
						}
					}
				}
			}
			maxDist = 0;
			for (int i = 0; i < chf.spanCount; ++i)
				maxDist = rcMax(src[i], maxDist);
		}
		public static ushort* boxBlur(ref rcCompactHeightfield chf, int thr, ushort* src, ushort* dst)
		{
			int w = chf.width;
			int h = chf.height;
			thr *= 2;
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						ushort cd = src[i];
						if (cd <= thr)
						{
							dst[i] = cd;
							continue;
						}
						int d = (int)cd;
						for (int dir = 0; dir < 4; ++dir)
						{
							if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
							{
								int ax = x + rcGetDirOffsetX(dir);
								int ay = y + rcGetDirOffsetY(dir);
								int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
								d += (int)src[ai];
								ref rcCompactSpan as1 = ref chf.spans[ai];
								int dir2 = (dir + 1) & 0x3;
								if (rcGetCon(ref as1, dir2) != RC_NOT_CONNECTED)
								{
									int ax2 = ax + rcGetDirOffsetX(dir2);
									int ay2 = ay + rcGetDirOffsetY(dir2);
									int ai2 = (int)chf.cells[ax2 + ay2 * w].index + rcGetCon(ref as1, dir2);
									d += (int)src[ai2];
								}
								else
								{
									d += cd;
								}
							}
							else
							{
								d += cd * 2;
							}
						}
						dst[i] = (ushort)((d + 5) / 9);
					}
				}
			}
			return dst;
		}
		public static bool floodRegion(int x, int y, int i, ushort level, ushort r, ref rcCompactHeightfield chf, ushort* srcReg, ushort* srcDist, ref rcTempVector<LevelStackEntry> stack)
		{
			int w = chf.width;
			byte area = chf.areas[i];
			stack.clear();
			stack.push_back(new LevelStackEntry(x, y, i));
			srcReg[i] = r;
			srcDist[i] = 0;
			ushort lev = (ushort)(level >= 2 ? level - 2 : 0);
			int count = 0;
			while (stack.size() > 0)
			{
				ref LevelStackEntry back = ref stack.back();
				int cx = back.x;
				int cy = back.y;
				int ci = back.index;
				stack.pop_back();
				ref rcCompactSpan cs = ref chf.spans[ci];
				ushort ar = 0;
				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(ref cs, dir) != RC_NOT_CONNECTED)
					{
						int ax = cx + rcGetDirOffsetX(dir);
						int ay = cy + rcGetDirOffsetY(dir);
						int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref cs, dir);
						if (chf.areas[ai] != area)
							continue;
						ushort nr = srcReg[ai];
						if (0 != (nr & RC_BORDER_REG))
							continue;
						if (nr != 0 && nr != r)
						{
							ar = nr;
							break;
						}
						ref rcCompactSpan as1 = ref chf.spans[ai];
						int dir2 = (dir + 1) & 0x3;
						if (rcGetCon(ref as1, dir2) != RC_NOT_CONNECTED)
						{
							int ax2 = ax + rcGetDirOffsetX(dir2);
							int ay2 = ay + rcGetDirOffsetY(dir2);
							int ai2 = (int)chf.cells[ax2 + ay2 * w].index + rcGetCon(ref as1, dir2);
							if (chf.areas[ai2] != area)
								continue;
							ushort nr2 = srcReg[ai2];
							if (nr2 != 0 && nr2 != r)
							{
								ar = nr2;
								break;
							}
						}
					}
				}
				if (ar != 0)
				{
					srcReg[ci] = 0;
					continue;
				}
				count++;
				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(ref cs, dir) != RC_NOT_CONNECTED)
					{
						int ax = cx + rcGetDirOffsetX(dir);
						int ay = cy + rcGetDirOffsetY(dir);
						int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref cs, dir);
						if (chf.areas[ai] != area)
							continue;
						if (chf.dist[ai] >= lev && srcReg[ai] == 0)
						{
							srcReg[ai] = r;
							srcDist[ai] = 0;
							stack.push_back(new LevelStackEntry(ax, ay, ai));
						}
					}
				}
			}
			return count > 0;
		}
		public static void expandRegions(int maxIter, ushort level, ref rcCompactHeightfield chf, ushort* srcReg, ushort* srcDist, ref rcTempVector<LevelStackEntry> stack, bool fillStack)
		{
			int w = chf.width;
			int h = chf.height;
			if (fillStack)
			{
				stack.clear();
				for (int y = 0; y < h; ++y)
				{
					for (int x = 0; x < w; ++x)
					{
						ref rcCompactCell c = ref chf.cells[x + y * w];
						for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
						{
							if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_null_AREA)
							{
								stack.push_back(new LevelStackEntry(x, y, i));
							}
						}
					}
				}
			}
			else
			{
				for (int j = 0; j < stack.size(); j++)
				{
					int i = stack[j].index;
					if (srcReg[i] != 0)
						stack[j].index = -1;
				}
			}
			rcTempVector<DirtyEntry> dirtyEntries = new rcTempVector<DirtyEntry>();
			int iter = 0;
			while (stack.size() > 0)
			{
				int failed = 0;
				dirtyEntries.clear();
				for (int j = 0; j < stack.size(); j++)
				{
					int x = stack[j].x;
					int y = stack[j].y;
					int i = stack[j].index;
					if (i < 0)
					{
						failed++;
						continue;
					}
					ushort r = srcReg[i];
					ushort d2 = 0xffff;
					byte area = chf.areas[i];
					ref rcCompactSpan s = ref chf.spans[i];
					for (int dir = 0; dir < 4; ++dir)
					{
						if (rcGetCon(ref s, dir) == RC_NOT_CONNECTED) continue;
						int ax = x + rcGetDirOffsetX(dir);
						int ay = y + rcGetDirOffsetY(dir);
						int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
						if (chf.areas[ai] != area) continue;
						if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0)
						{
							if (srcDist[ai] + 2 < d2)
							{
								r = srcReg[ai];
								d2 = (ushort)(srcDist[ai] + 2);
							}
						}
					}
					if (r != 0)
					{
						stack[j].index = -1;
						dirtyEntries.push_back(new DirtyEntry(i, r, d2));
					}
					else
					{
						failed++;
					}
				}
				for (int i = 0; i < dirtyEntries.size(); i++)
				{
					int idx = dirtyEntries[i].index;
					srcReg[idx] = dirtyEntries[i].region;
					srcDist[idx] = dirtyEntries[i].distance2;
				}
				if (failed == stack.size())
					break;
				if (level > 0)
				{
					++iter;
					if (iter >= maxIter)
						break;
				}
			}
		}
		public static void sortCellsByLevel(ushort startLevel, ref rcCompactHeightfield chf, ushort* srcReg, uint nbStacks, rcTempVector<LevelStackEntry>[] stacks, ushort loglevelsPerStack)
		{
			int w = chf.width;
			int h = chf.height;
			startLevel = (ushort)(startLevel >> loglevelsPerStack);
			for (uint j = 0; j < nbStacks; ++j)
				stacks[j].clear();
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						if (chf.areas[i] == RC_null_AREA || srcReg[i] != 0)
							continue;
						int level = chf.dist[i] >> loglevelsPerStack;
						int sId = startLevel - level;
						if (sId >= (int)nbStacks)
							continue;
						if (sId < 0)
							sId = 0;
						stacks[sId].push_back(new LevelStackEntry(x, y, i));
					}
				}
			}
		}
		public static void appendStacks(ref rcTempVector<LevelStackEntry> srcStack, ref rcTempVector<LevelStackEntry> dstStack, ushort* srcReg)
		{
			for (int j = 0; j < srcStack.size(); j++)
			{
				int i = srcStack[j].index;
				if ((i < 0) || (srcReg[i] != 0))
					continue;
				dstStack.push_back(srcStack[j]);
			}
		}
		public static void removeAdjacentNeighbours(ref rcRegion reg)
		{
			for (int i = 0; i < reg.connections.size() && reg.connections.size() > 1;)
			{
				int ni = (i + 1) % reg.connections.size();
				if (reg.connections[i] == reg.connections[ni])
				{
					for (int j = i; j < reg.connections.size() - 1; ++j)
						reg.connections[j] = reg.connections[j + 1];
					reg.connections.pop();
				}
				else
					++i;
			}
		}
		public static void replaceNeighbour(ref rcRegion reg, ushort oldId, ushort newId)
		{
			bool neiChanged = false;
			for (int i = 0; i < reg.connections.size(); ++i)
			{
				if (reg.connections[i] == oldId)
				{
					reg.connections[i] = newId;
					neiChanged = true;
				}
			}
			for (int i = 0; i < reg.floors.size(); ++i)
			{
				if (reg.floors[i] == oldId)
					reg.floors[i] = newId;
			}
			if (neiChanged)
				removeAdjacentNeighbours(ref reg);
		}
		public static bool canMergeWithRegion(ref rcRegion rega, ref rcRegion regb)
		{
			if (rega.areaType != regb.areaType)
				return false;
			int n = 0;
			for (int i = 0; i < rega.connections.size(); ++i)
			{
				if (rega.connections[i] == regb.id)
					n++;
			}
			if (n > 1)
				return false;
			for (int i = 0; i < rega.floors.size(); ++i)
			{
				if (rega.floors[i] == regb.id)
					return false;
			}
			return true;
		}
		public static void addUniqueFloorRegion(ref rcRegion reg, int n)
		{
			for (int i = 0; i < reg.floors.size(); ++i)
				if (reg.floors[i] == n)
					return;
			reg.floors.push(n);
		}
		public static bool mergeRegions(ref rcRegion rega, ref rcRegion regb)
		{
			ushort aid = rega.id;
			ushort bid = regb.id;
			rcIntArray acon = new rcIntArray();
			acon.resize(rega.connections.size());
			for (int i = 0; i < rega.connections.size(); ++i)
				acon[i] = rega.connections[i];
			ref rcIntArray bcon = ref regb.connections;
			int insa = -1;
			for (int i = 0; i < acon.size(); ++i)
			{
				if (acon[i] == bid)
				{
					insa = i;
					break;
				}
			}
			if (insa == -1)
				return false;
			int insb = -1;
			for (int i = 0; i < bcon.size(); ++i)
			{
				if (bcon[i] == aid)
				{
					insb = i;
					break;
				}
			}
			if (insb == -1)
				return false;
			rega.connections.clear();
			for (int i = 0, ni = acon.size(); i < ni - 1; ++i)
				rega.connections.push(acon[(insa + 1 + i) % ni]);
			for (int i = 0, ni = bcon.size(); i < ni - 1; ++i)
				rega.connections.push(bcon[(insb + 1 + i) % ni]);
			removeAdjacentNeighbours(ref rega);
			for (int j = 0; j < regb.floors.size(); ++j)
				addUniqueFloorRegion(ref rega, regb.floors[j]);
			rega.spanCount += regb.spanCount;
			regb.spanCount = 0;
			regb.connections.resize(0);
			return true;
		}
		public static bool isRegionConnectedToBorder(ref rcRegion reg)
		{
			for (int i = 0; i < reg.connections.size(); ++i)
			{
				if (reg.connections[i] == 0)
					return true;
			}
			return false;
		}
		public static bool isSolidEdge(ref rcCompactHeightfield chf, ushort* srcReg, int x, int y, int i, int dir)
		{
			ref rcCompactSpan s = ref chf.spans[i];
			ushort r = 0;
			if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
			{
				int ax = x + rcGetDirOffsetX(dir);
				int ay = y + rcGetDirOffsetY(dir);
				int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref s, dir);
				r = srcReg[ai];
			}
			if (r == srcReg[i])
				return false;
			return true;
		}
		public static void walkContour(int x, int y, int i, int dir, ref rcCompactHeightfield chf, ushort* srcReg, ref rcIntArray cont)
		{
			int startDir = dir;
			int starti = i;
			ref rcCompactSpan ss = ref chf.spans[i];
			ushort curReg = 0;
			if (rcGetCon(ref ss, dir) != RC_NOT_CONNECTED)
			{
				int ax = x + rcGetDirOffsetX(dir);
				int ay = y + rcGetDirOffsetY(dir);
				int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref ss, dir);
				curReg = srcReg[ai];
			}
			cont.push(curReg);
			int iter = 0;
			while (++iter < 40000)
			{
				ref rcCompactSpan s = ref chf.spans[i];
				if (isSolidEdge(ref chf, srcReg, x, y, i, dir))
				{
					ushort r = 0;
					if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
					{
						int ax = x + rcGetDirOffsetX(dir);
						int ay = y + rcGetDirOffsetY(dir);
						int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(ref s, dir);
						r = srcReg[ai];
					}
					if (r != curReg)
					{
						curReg = r;
						cont.push(curReg);
					}
					dir = (dir + 1) & 0x3;
				}
				else
				{
					int ni = -1;
					int nx = x + rcGetDirOffsetX(dir);
					int ny = y + rcGetDirOffsetY(dir);
					if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
					{
						ref rcCompactCell nc = ref chf.cells[nx + ny * chf.width];
						ni = (int)nc.index + rcGetCon(ref s, dir);
					}
					if (ni == -1)
					{
						return;
					}
					x = nx;
					y = ny;
					i = ni;
					dir = (dir + 3) & 0x3;
				}
				if (starti == i && startDir == dir)
				{
					break;
				}
			}
			if (cont.size() > 1)
			{
				for (int j = 0; j < cont.size();)
				{
					int nj = (j + 1) % cont.size();
					if (cont[j] == cont[nj])
					{
						for (int k = j; k < cont.size() - 1; ++k)
							cont[k] = cont[k + 1];
						cont.pop();
					}
					else
						++j;
				}
			}
		}
		public static bool mergeAndFilterRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize, ref ushort maxRegionId, ref rcCompactHeightfield chf, ushort* srcReg, ref rcIntArray overlaps)
		{
			int w = chf.width;
			int h = chf.height;
			int nreg = maxRegionId + 1;
			//var regions = new rcTempVector<rcRegion>();
			//if (!regions.reserve(nreg))
			//{
			//	//ctx->log(RC_LOG_ERROR, "mergeAndFilterRegions: Out of memory 'regions' (%d).", nreg);
			//	return false;
			//}
			var regions = new rcRegion[nreg];
			for (int i = 0; i < nreg; ++i)
			{
				//regions.push_back(new rcRegion((ushort)i));
				regions[i] = new rcRegion((ushort)i);
			}
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ushort r = srcReg[i];
						if (r == 0 || r >= nreg)
							continue;
						//ref rcRegion reg = ref regions[r];
						ref rcRegion reg = ref regions[r];
						reg.spanCount++;
						for (int j = (int)c.index; j < ni; ++j)
						{
							if (i == j) continue;
							ushort floorId = srcReg[j];
							if (floorId == 0 || floorId >= nreg)
								continue;
							if (floorId == r)
								reg.overlap = true;
							addUniqueFloorRegion(ref reg, floorId);
						}
						if (reg.connections.size() > 0)
							continue;
						reg.areaType = chf.areas[i];
						int ndir = -1;
						for (int dir = 0; dir < 4; ++dir)
						{
							if (isSolidEdge(ref chf, srcReg, x, y, i, dir))
							{
								ndir = dir;
								break;
							}
						}
						if (ndir != -1)
						{
							walkContour(x, y, i, ndir, ref chf, srcReg, ref reg.connections);
						}
					}
				}
			}
			rcIntArray stack = new rcIntArray(32);
			rcIntArray trace = new rcIntArray(32);
			for (int i = 0; i < nreg; ++i)
			{
				ref rcRegion reg = ref regions[i];
				if (reg.id == 0 || (reg.id & RC_BORDER_REG) != 0)
					continue;
				if (reg.spanCount == 0)
					continue;
				if (reg.visited)
					continue;
				bool connectsToBorder = false;
				int spanCount = 0;
				stack.clear();
				trace.clear();
				reg.visited = true;
				stack.push(i);
				while (stack.size() != 0)
				{
					int ri = stack.pop();
					ref rcRegion creg = ref regions[ri];
					spanCount += creg.spanCount;
					trace.push(ri);
					for (int j = 0; j < creg.connections.size(); ++j)
					{
						if (0 != (creg.connections[j] & RC_BORDER_REG))
						{
							connectsToBorder = true;
							continue;
						}
						ref rcRegion neireg = ref regions[creg.connections[j]];
						if (neireg.visited)
							continue;
						if (neireg.id == 0 || 0 != (neireg.id & RC_BORDER_REG))
							continue;
						stack.push(neireg.id);
						neireg.visited = true;
					}
				}
				if (spanCount < minRegionArea && !connectsToBorder)
				{
					for (int j = 0; j < trace.size(); ++j)
					{
						regions[trace[j]].spanCount = 0;
						regions[trace[j]].id = 0;
					}
				}
			}
			int mergeCount = 0;
			do
			{
				mergeCount = 0;
				for (int i = 0; i < nreg; ++i)
				{
					ref rcRegion reg = ref regions[i];
					if (reg.id == 0 || 0 != (reg.id & RC_BORDER_REG))
						continue;
					if (reg.overlap)
						continue;
					if (reg.spanCount == 0)
						continue;
					if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(ref reg))
						continue;
					int smallest = 0xfffffff;
					ushort mergeId = reg.id;
					for (int j = 0; j < reg.connections.size(); ++j)
					{
						if (0 != (reg.connections[j] & RC_BORDER_REG)) continue;
						ref rcRegion mreg = ref regions[reg.connections[j]];
						if (mreg.id == 0 || (mreg.id & RC_BORDER_REG) != 0 || mreg.overlap) continue;
						if (mreg.spanCount < smallest &&
						canMergeWithRegion(ref reg, ref mreg) &&
						canMergeWithRegion(ref mreg, ref reg))
						{
							smallest = mreg.spanCount;
							mergeId = mreg.id;
						}
					}
					if (mergeId != reg.id)
					{
						ushort oldId = reg.id;
						ref rcRegion target = ref regions[mergeId];
						if (mergeRegions(ref target, ref reg))
						{
							for (int j = 0; j < nreg; ++j)
							{
								if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG) != 0) continue;
								if (regions[j].id == oldId)
									regions[j].id = mergeId;
								replaceNeighbour(ref regions[j], oldId, mergeId);
							}
							mergeCount++;
						}
					}
				}
			}
			while (mergeCount > 0);
			for (int i = 0; i < nreg; ++i)
			{
				regions[i].remap = false;
				if (regions[i].id == 0) continue;
				if ((regions[i].id & RC_BORDER_REG) != 0) continue;
				regions[i].remap = true;
			}
			ushort regIdGen = 0;
			for (int i = 0; i < nreg; ++i)
			{
				if (!regions[i].remap)
					continue;
				ushort oldId = regions[i].id;
				ushort newId = ++regIdGen;
				for (int j = i; j < nreg; ++j)
				{
					if (regions[j].id == oldId)
					{
						regions[j].id = newId;
						regions[j].remap = false;
					}
				}
			}
			maxRegionId = regIdGen;
			for (int i = 0; i < chf.spanCount; ++i)
			{
				if ((srcReg[i] & RC_BORDER_REG) == 0)
					srcReg[i] = regions[srcReg[i]].id;
			}
			for (int i = 0; i < nreg; ++i)
				if (regions[i].overlap)
					overlaps.push(regions[i].id);
			return true;
		}
		public static void addUniqueConnection(ref rcRegion reg, int n)
		{
			for (int i = 0; i < reg.connections.size(); ++i)
				if (reg.connections[i] == n)
					return;
			reg.connections.push(n);
		}
		public static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, ref ushort maxRegionId, ref rcCompactHeightfield chf, ushort* srcReg)
		{
			int w = chf.width;
			int h = chf.height;
			int nreg = maxRegionId + 1;
			rcTempVector<rcRegion> regions = new rcTempVector<rcRegion>();
			if (!regions.reserve(nreg))
			{
				//ctx->log(RC_LOG_ERROR, "mergeAndFilterLayerRegions: Out of memory 'regions' (%d).", nreg);
				return false;
			}
			for (int i = 0; i < nreg; ++i)
				regions.push_back(new rcRegion((ushort)i));
			rcIntArray lregs = new rcIntArray(32);
			for (int y = 0; y < h; ++y)
			{
				for (int x = 0; x < w; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					lregs.clear();
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						ref rcCompactSpan s = ref chf.spans[i];
						ushort ri = srcReg[i];
						if (ri == 0 || ri >= nreg) continue;
						ref rcRegion reg = ref regions[ri];
						reg.spanCount++;
						reg.ymin = rcMin(reg.ymin, s.y);
						reg.ymax = rcMax(reg.ymax, s.y);
						lregs.push(ri);
						for (int dir = 0; dir < 4; ++dir)
						{
							if (rcGetCon(ref s, dir) != RC_NOT_CONNECTED)
							{
								int ax = x + rcGetDirOffsetX(dir);
								int ay = y + rcGetDirOffsetY(dir);
								int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(ref s, dir);
								ushort rai = srcReg[ai];
								if (rai > 0 && rai < nreg && rai != ri)
									addUniqueConnection(ref reg, rai);
								if (0 != (rai & RC_BORDER_REG))
									reg.connectsToBorder = true;
							}
						}
					}
					for (int i = 0; i < lregs.size() - 1; ++i)
					{
						for (int j = i + 1; j < lregs.size(); ++j)
						{
							if (lregs[i] != lregs[j])
							{
								ref rcRegion ri = ref regions[lregs[i]];
								ref rcRegion rj = ref regions[lregs[j]];
								addUniqueFloorRegion(ref ri, lregs[j]);
								addUniqueFloorRegion(ref rj, lregs[i]);
							}
						}
					}
				}
			}
			ushort layerId = 1;
			for (int i = 0; i < nreg; ++i)
				regions[i].id = 0;
			rcIntArray stack = new rcIntArray(32);
			for (int i = 1; i < nreg; ++i)
			{
				ref rcRegion root = ref regions[i];
				if (root.id != 0)
					continue;
				root.id = layerId;
				stack.clear();
				stack.push(i);
				while (stack.size() > 0)
				{
					ref rcRegion reg = ref regions[stack[0]];
					for (int j = 0; j < stack.size() - 1; ++j)
						stack[j] = stack[j + 1];
					stack.resize(stack.size() - 1);
					int ncons = (int)reg.connections.size();
					for (int j = 0; j < ncons; ++j)
					{
						int nei = reg.connections[j];
						ref rcRegion regn = ref regions[nei];
						if (regn.id != 0)
							continue;
						bool overlap = false;
						for (int k = 0; k < root.floors.size(); k++)
						{
							if (root.floors[k] == nei)
							{
								overlap = true;
								break;
							}
						}
						if (overlap)
							continue;
						stack.push(nei);
						regn.id = layerId;
						for (int k = 0; k < regn.floors.size(); ++k)
							addUniqueFloorRegion(ref root, regn.floors[k]);
						root.ymin = rcMin(root.ymin, regn.ymin);
						root.ymax = rcMax(root.ymax, regn.ymax);
						root.spanCount += regn.spanCount;
						regn.spanCount = 0;
						root.connectsToBorder = root.connectsToBorder || regn.connectsToBorder;
					}
				}
				layerId++;
			}
			for (int i = 0; i < nreg; ++i)
			{
				if (regions[i].spanCount > 0 && regions[i].spanCount < minRegionArea && !regions[i].connectsToBorder)
				{
					ushort reg = regions[i].id;
					for (int j = 0; j < nreg; ++j)
						if (regions[j].id == reg)
							regions[j].id = 0;
				}
			}
			for (int i = 0; i < nreg; ++i)
			{
				regions[i].remap = false;
				if (regions[i].id == 0) continue;
				if (0 != (regions[i].id & RC_BORDER_REG)) continue;
				regions[i].remap = true;
			}
			ushort regIdGen = 0;
			for (int i = 0; i < nreg; ++i)
			{
				if (!regions[i].remap)
					continue;
				ushort oldId = regions[i].id;
				ushort newId = ++regIdGen;
				for (int j = i; j < nreg; ++j)
				{
					if (regions[j].id == oldId)
					{
						regions[j].id = newId;
						regions[j].remap = false;
					}
				}
			}
			maxRegionId = regIdGen;
			for (int i = 0; i < chf.spanCount; ++i)
			{
				if ((srcReg[i] & RC_BORDER_REG) == 0)
					srcReg[i] = regions[srcReg[i]].id;
			}
			return true;
		}
		public static void paintRectRegion(int minx, int maxx, int miny, int maxy, ushort regId, ref rcCompactHeightfield chf, ushort* srcReg)
		{
			int w = chf.width;
			for (int y = miny; y < maxy; ++y)
			{
				for (int x = minx; x < maxx; ++x)
				{
					ref rcCompactCell c = ref chf.cells[x + y * w];
					for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
					{
						if (chf.areas[i] != RC_null_AREA)
							srcReg[i] = regId;
					}
				}
			}
		}
	}
}
